402aa52362
qc rt target api now uses names instead of numbers. shaders can name such rendertargets in advance. added timing info to qc profiling, instead of just opcodes executed. added sv_showconnectionlessmessages, to show all messages that don't relate to a client, so omc can see if he's getting DDOSed easily. try to show proper error messages with the xmpp plugin, at least for certain things. git-svn-id: https://svn.code.sf.net/p/fteqw/code/trunk@4714 fc73d0e0-1445-4013-8a0c-d673dee63da5
419 lines
8.2 KiB
C
419 lines
8.2 KiB
C
#include "quakedef.h"
|
|
|
|
//client may remap messages from the server to a regional bit of text.
|
|
//server may remap progs messages
|
|
|
|
//basic language is english (cos that's what (my version of) Quake uses).
|
|
//translate is english->lang
|
|
//untranslate is lang->english for console commands.
|
|
|
|
|
|
|
|
char sys_language[64] = "";
|
|
struct language_s languages[MAX_LANGUAGES];
|
|
|
|
void TL_LanguageChanged(struct cvar_s *var, char *oldvalue)
|
|
{
|
|
#ifndef CLIENTONLY
|
|
svs.language = TL_FindLanguage(var->string);
|
|
#endif
|
|
#ifndef SERVERONLY
|
|
cls.language = TL_FindLanguage(var->string);
|
|
#endif
|
|
}
|
|
|
|
cvar_t language = CVARFC("lang", sys_language, CVAR_USERINFO, TL_LanguageChanged);
|
|
|
|
void TranslateInit(void)
|
|
{
|
|
Cvar_Register(&language, "International variables");
|
|
}
|
|
|
|
void TL_Shutdown(void)
|
|
{
|
|
int j;
|
|
|
|
for (j = 0; j < MAX_LANGUAGES; j++)
|
|
{
|
|
if (!languages[j].name)
|
|
continue;
|
|
free(languages[j].name);
|
|
languages[j].name = NULL;
|
|
PO_Close(languages[j].po);
|
|
languages[j].po = NULL;
|
|
}
|
|
}
|
|
|
|
static int TL_LoadLanguage(char *lang)
|
|
{
|
|
vfsfile_t *f;
|
|
int j;
|
|
char *u;
|
|
for (j = 0; j < MAX_LANGUAGES; j++)
|
|
{
|
|
if (!languages[j].name)
|
|
break;
|
|
if (!stricmp(languages[j].name, lang))
|
|
return j;
|
|
}
|
|
|
|
//err... oops, ran out of languages...
|
|
if (j == MAX_LANGUAGES)
|
|
return 0;
|
|
|
|
if (*lang)
|
|
f = FS_OpenVFS(va("fteqw.%s.po", lang), "rb", FS_BINARYPATH);
|
|
else
|
|
f = NULL;
|
|
if (!f && *lang)
|
|
{
|
|
//keep truncating until we can find a name that works
|
|
u = strrchr(lang, '_');
|
|
if (u)
|
|
*u = 0;
|
|
else
|
|
lang = "";
|
|
return TL_LoadLanguage(lang);
|
|
}
|
|
languages[j].name = strdup(lang);
|
|
languages[j].po = f?PO_Load(f):NULL;
|
|
|
|
return j;
|
|
}
|
|
int TL_FindLanguage(const char *lang)
|
|
{
|
|
char trimname[64];
|
|
Q_strncpyz(trimname, lang, sizeof(trimname));
|
|
return TL_LoadLanguage(trimname);
|
|
}
|
|
|
|
//need to set up default languages for any early prints before cvars are inited.
|
|
void TL_InitLanguages(void)
|
|
{
|
|
int i;
|
|
char *lang;
|
|
|
|
//lang can override any environment or system settings.
|
|
if ((i = COM_CheckParm("-lang")))
|
|
Q_strncpyz(sys_language, com_argv[i+1], sizeof(sys_language));
|
|
else
|
|
{
|
|
lang = NULL;
|
|
if (!lang)
|
|
lang = getenv("LANGUAGE");
|
|
if (!lang)
|
|
lang = getenv("LC_ALL");
|
|
if (!lang)
|
|
lang = getenv("LC_MESSAGES");
|
|
if (!lang)
|
|
lang = getenv("LANG");
|
|
if (!lang)
|
|
lang = "";
|
|
if (!strcmp(lang, "C") || !strcmp(lang, "POSIX"))
|
|
lang = "";
|
|
|
|
//windows will have already set the locale from the windows settings, so only replace it if its actually valid.
|
|
if (*lang)
|
|
Q_strncpyz(sys_language, lang, sizeof(sys_language));
|
|
}
|
|
|
|
//clean it up.
|
|
//takes the form: [language[_territory][.codeset][@modifier]]
|
|
//we don't understand modifiers
|
|
lang = strrchr(sys_language, '@');
|
|
if (lang)
|
|
*lang = 0;
|
|
//we don't understand codesets sadly.
|
|
lang = strrchr(sys_language, '.');
|
|
if (lang)
|
|
*lang = 0;
|
|
//we also only support the single primary locale (no fallbacks, we're just using the language[+territory])
|
|
lang = strrchr(sys_language, ':');
|
|
if (lang)
|
|
*lang = 0;
|
|
//but we do support territories.
|
|
|
|
#ifndef CLIENTONLY
|
|
svs.language = TL_FindLanguage(sys_language);
|
|
#endif
|
|
#ifndef SERVERONLY
|
|
cls.language = TL_FindLanguage(sys_language);
|
|
#endif
|
|
|
|
//make sure a fallback exists, but not as language 0
|
|
TL_FindLanguage("");
|
|
}
|
|
|
|
|
|
|
|
|
|
//#ifndef CLIENTONLY
|
|
//this stuff is for hexen2 translation strings.
|
|
//(hexen2 is uuuuggllyyyy...)
|
|
static char *strings_list;
|
|
static char **strings_table;
|
|
static int strings_count;
|
|
static qboolean strings_loaded;
|
|
void T_FreeStrings(void)
|
|
{ //on map change, following gamedir change
|
|
if (strings_loaded)
|
|
{
|
|
BZ_Free(strings_list);
|
|
BZ_Free(strings_table);
|
|
strings_count = 0;
|
|
strings_loaded = false;
|
|
}
|
|
}
|
|
void T_LoadString(void)
|
|
{
|
|
int i;
|
|
char *s, *s2;
|
|
//count new lines
|
|
strings_loaded = true;
|
|
strings_count = 0;
|
|
strings_list = FS_LoadMallocFile("strings.txt");
|
|
if (!strings_list)
|
|
return;
|
|
|
|
for (s = strings_list; *s; s++)
|
|
{
|
|
if (*s == '\n')
|
|
strings_count++;
|
|
}
|
|
strings_table = BZ_Malloc(sizeof(char*)*strings_count);
|
|
|
|
s = strings_list;
|
|
for (i = 0; i < strings_count; i++)
|
|
{
|
|
strings_table[i] = s;
|
|
s2 = strchr(s, '\n');
|
|
if (!s2)
|
|
break;
|
|
|
|
while (s < s2)
|
|
{
|
|
if (*s == '\r')
|
|
*s = '\0';
|
|
else if (*s == '^' || *s == '@') //becomes new line
|
|
*s = '\n';
|
|
s++;
|
|
}
|
|
s = s2+1;
|
|
*s2 = '\0';
|
|
}
|
|
}
|
|
char *T_GetString(int num)
|
|
{
|
|
if (!strings_loaded)
|
|
{
|
|
T_LoadString();
|
|
}
|
|
if (num<0 || num >= strings_count)
|
|
return "BAD STRING";
|
|
|
|
return strings_table[num];
|
|
}
|
|
//#endif
|
|
|
|
#ifndef SERVERONLY
|
|
//for hexen2's objectives and stuff.
|
|
static char *info_strings_list;
|
|
static char **info_strings_table;
|
|
static int info_strings_count;
|
|
static qboolean info_strings_loaded;
|
|
void T_FreeInfoStrings(void)
|
|
{ //on map change, following gamedir change
|
|
if (info_strings_loaded)
|
|
{
|
|
BZ_Free(info_strings_list);
|
|
BZ_Free(info_strings_table);
|
|
info_strings_count = 0;
|
|
info_strings_loaded = false;
|
|
}
|
|
}
|
|
void T_LoadInfoString(void)
|
|
{
|
|
int i;
|
|
char *s, *s2;
|
|
//count new lines
|
|
info_strings_loaded = true;
|
|
info_strings_count = 0;
|
|
info_strings_list = FS_LoadMallocFile("infolist.txt");
|
|
if (!info_strings_list)
|
|
return;
|
|
|
|
for (s = info_strings_list; *s; s++)
|
|
{
|
|
if (*s == '\n')
|
|
info_strings_count++;
|
|
}
|
|
info_strings_table = BZ_Malloc(sizeof(char*)*info_strings_count);
|
|
|
|
s = info_strings_list;
|
|
for (i = 0; i < info_strings_count; i++)
|
|
{
|
|
info_strings_table[i] = s;
|
|
s2 = strchr(s, '\n');
|
|
if (!s2)
|
|
break;
|
|
|
|
while (s < s2)
|
|
{
|
|
if (*s == '\r')
|
|
*s = '\0';
|
|
else if (*s == '^' || *s == '@') //becomes new line
|
|
*s = '\n';
|
|
s++;
|
|
}
|
|
s = s2+1;
|
|
*s2 = '\0';
|
|
}
|
|
}
|
|
char *T_GetInfoString(int num)
|
|
{
|
|
if (!info_strings_loaded)
|
|
{
|
|
T_LoadInfoString();
|
|
}
|
|
if (num<0 || num >= info_strings_count)
|
|
return "BAD STRING";
|
|
|
|
return info_strings_table[num];
|
|
}
|
|
#endif
|
|
|
|
struct poline_s
|
|
{
|
|
bucket_t buck;
|
|
struct poline_s *next;
|
|
char *orig;
|
|
char *translated;
|
|
};
|
|
|
|
struct po_s
|
|
{
|
|
hashtable_t hash;
|
|
|
|
struct poline_s *lines;
|
|
};
|
|
|
|
const char *PO_GetText(struct po_s *po, const char *msg)
|
|
{
|
|
struct poline_s *line;
|
|
if (!po)
|
|
return msg;
|
|
line = Hash_Get(&po->hash, msg);
|
|
if (line)
|
|
return line->translated;
|
|
return msg;
|
|
}
|
|
static void PO_AddText(struct po_s *po, const char *orig, const char *trans)
|
|
{
|
|
size_t olen = strlen(orig)+1;
|
|
size_t tlen = strlen(trans)+1;
|
|
struct poline_s *line = Z_Malloc(sizeof(*line)+olen+tlen);
|
|
memcpy(line+1, orig, olen);
|
|
orig = (const char*)(line+1);
|
|
line->translated = (char*)(line+1)+olen;
|
|
memcpy(line->translated, trans, tlen);
|
|
trans = (const char*)(line->translated);
|
|
Hash_Add(&po->hash, orig, line, &line->buck);
|
|
|
|
line->next = po->lines;
|
|
po->lines = line;
|
|
}
|
|
struct po_s *PO_Load(vfsfile_t *file)
|
|
{
|
|
struct po_s *po;
|
|
unsigned int buckets = 1024;
|
|
char *instart, *in, *end;
|
|
int inlen;
|
|
char msgid[32768];
|
|
char msgstr[32768];
|
|
|
|
qboolean allowblanks = !!COM_CheckParm("-translatetoblank");
|
|
|
|
po = Z_Malloc(sizeof(*po) + Hash_BytesForBuckets(buckets));
|
|
Hash_InitTable(&po->hash, buckets, po+1);
|
|
|
|
inlen = file?VFS_GETLEN(file):0;
|
|
instart = in = BZ_Malloc(inlen+1);
|
|
if (file)
|
|
VFS_READ(file, in, inlen);
|
|
in[inlen] = 0;
|
|
if (file)
|
|
VFS_CLOSE(file);
|
|
|
|
end = in + inlen;
|
|
while(in < end)
|
|
{
|
|
while(*in == ' ' || *in == '\n' || *in == '\r' || *in == '\t')
|
|
in++;
|
|
if (*in == '#')
|
|
{
|
|
while (*in && *in != '\n')
|
|
in++;
|
|
}
|
|
else if (!strncmp(in, "msgid", 5) && (in[5] == ' ' || in[5] == '\t' || in[5] == '\r' || in[5] == '\n'))
|
|
{
|
|
size_t start = 0;
|
|
size_t ofs = 0;
|
|
in += 5;
|
|
while(1)
|
|
{
|
|
while(*in == ' ' || *in == '\n' || *in == '\r' || *in == '\t')
|
|
in++;
|
|
if (*in == '\"')
|
|
{
|
|
in = COM_ParseCString(in, msgid+start, sizeof(msgid) - start, &ofs);
|
|
start += ofs;
|
|
}
|
|
else
|
|
break;
|
|
}
|
|
}
|
|
else if (!strncmp(in, "msgstr", 6) && (in[6] == ' ' || in[6] == '\t' || in[6] == '\r' || in[6] == '\n'))
|
|
{
|
|
size_t start = 0;
|
|
size_t ofs = 0;
|
|
in += 6;
|
|
while(1)
|
|
{
|
|
while(*in == ' ' || *in == '\n' || *in == '\r' || *in == '\t')
|
|
in++;
|
|
if (*in == '\"')
|
|
{
|
|
in = COM_ParseCString(in, msgstr+start, sizeof(msgstr) - start, &ofs);
|
|
start += ofs;
|
|
}
|
|
else
|
|
break;
|
|
}
|
|
|
|
if ((*msgid && start) || allowblanks)
|
|
PO_AddText(po, msgid, msgstr);
|
|
}
|
|
else
|
|
{
|
|
//some sort of junk?
|
|
in++;
|
|
while (*in && *in != '\n')
|
|
in++;
|
|
}
|
|
}
|
|
|
|
BZ_Free(instart);
|
|
return po;
|
|
}
|
|
void PO_Close(struct po_s *po)
|
|
{
|
|
if (!po)
|
|
return;
|
|
while(po->lines)
|
|
{
|
|
struct poline_s *r = po->lines;
|
|
po->lines = r->next;
|
|
Z_Free(r);
|
|
}
|
|
Z_Free(po);
|
|
}
|