mirror of
https://github.com/Shpoike/Quakespasm.git
synced 2025-02-09 01:01:07 +00:00
Add basic localized strings support for 2021 re-release
See https://github.com/Novum/vkQuake/pull/345
This commit is contained in:
parent
2313298d14
commit
83af8d060f
4 changed files with 463 additions and 6 deletions
421
Quake/common.c
421
Quake/common.c
|
@ -2463,3 +2463,424 @@ long FS_filelength (fshandle_t *fh)
|
|||
return fh->length;
|
||||
}
|
||||
|
||||
/*
|
||||
============================================================================
|
||||
LOCALIZATION
|
||||
============================================================================
|
||||
*/
|
||||
typedef struct
|
||||
{
|
||||
char *key;
|
||||
char *value;
|
||||
} locentry_t;
|
||||
|
||||
typedef struct
|
||||
{
|
||||
int numentries;
|
||||
int maxnumentries;
|
||||
int numindices;
|
||||
unsigned *indices;
|
||||
locentry_t *entries;
|
||||
char *text;
|
||||
} localization_t;
|
||||
|
||||
static localization_t localization;
|
||||
|
||||
/*
|
||||
================
|
||||
COM_HashString
|
||||
Computes the FNV-1a hash of string str
|
||||
================
|
||||
*/
|
||||
unsigned COM_HashString (const char *str)
|
||||
{
|
||||
unsigned hash = 0x811c9dc5u;
|
||||
while (*str)
|
||||
{
|
||||
hash ^= *str++;
|
||||
hash *= 0x01000193u;
|
||||
}
|
||||
return hash;
|
||||
}
|
||||
|
||||
/*
|
||||
================
|
||||
LOC_LoadFile
|
||||
================
|
||||
*/
|
||||
void LOC_LoadFile (const char *file)
|
||||
{
|
||||
char path[1024];
|
||||
FILE *fp = NULL;
|
||||
int i,lineno;
|
||||
char *cursor;
|
||||
|
||||
// clear existing data
|
||||
if (localization.text)
|
||||
{
|
||||
free(localization.text);
|
||||
localization.text = NULL;
|
||||
}
|
||||
localization.numentries = 0;
|
||||
localization.numindices = 0;
|
||||
|
||||
if (!file || !*file)
|
||||
return;
|
||||
|
||||
Con_Printf("\nLanguage initialization\n");
|
||||
|
||||
q_snprintf(path, sizeof(path), "%s/%s", com_basedir, file);
|
||||
fp = fopen(path, "r");
|
||||
if (!fp) goto fail;
|
||||
fseek(fp, 0, SEEK_END);
|
||||
i = ftell(fp);
|
||||
if (i <= 0) goto fail;
|
||||
localization.text = (char *) calloc(1, i+1);
|
||||
if (!localization.text)
|
||||
{
|
||||
fail: if (fp) fclose(fp);
|
||||
Con_Printf("Couldn't load '%s'\nfrom '%s'\n", file, com_basedir);
|
||||
return;
|
||||
}
|
||||
fseek(fp, 0, SEEK_SET);
|
||||
fread(localization.text, 1, i, fp);
|
||||
fclose(fp);
|
||||
|
||||
cursor = localization.text;
|
||||
|
||||
// skip BOM
|
||||
if ((unsigned char)(cursor[0]) == 0xEF && (unsigned char)(cursor[1]) == 0xBB && cursor[2] == 0xB)
|
||||
cursor += 3;
|
||||
|
||||
lineno = 0;
|
||||
while (*cursor)
|
||||
{
|
||||
char *line, *equals;
|
||||
|
||||
lineno++;
|
||||
|
||||
// skip leading whitespace
|
||||
while (q_isblank(*cursor))
|
||||
++cursor;
|
||||
|
||||
line = cursor;
|
||||
equals = NULL;
|
||||
// find line end and first equals sign, if any
|
||||
while (*cursor && *cursor != '\n')
|
||||
{
|
||||
if (*cursor == '=' && !equals)
|
||||
equals = cursor;
|
||||
cursor++;
|
||||
}
|
||||
|
||||
if (line[0] == '/')
|
||||
{
|
||||
if (line[1] != '/')
|
||||
Con_DPrintf("LOC_LoadFile: malformed comment on line %d\n", lineno);
|
||||
}
|
||||
else if (equals)
|
||||
{
|
||||
char *key_end = equals;
|
||||
qboolean leading_quote;
|
||||
qboolean trailing_quote;
|
||||
locentry_t *entry;
|
||||
char *value_src;
|
||||
char *value_dst;
|
||||
char *value;
|
||||
|
||||
// trim whitespace before equals sign
|
||||
while (key_end != line && q_isspace(key_end[-1]))
|
||||
key_end--;
|
||||
*key_end = 0;
|
||||
|
||||
value = equals + 1;
|
||||
// skip whitespace after equals sign
|
||||
while (value != cursor && q_isspace(*value))
|
||||
value++;
|
||||
|
||||
leading_quote = (*value == '\"');
|
||||
trailing_quote = false;
|
||||
value += leading_quote;
|
||||
|
||||
// transform escape sequences in-place
|
||||
value_src = value;
|
||||
value_dst = value;
|
||||
while (value_src != cursor)
|
||||
{
|
||||
if (*value_src == '\\' && value_src + 1 != cursor)
|
||||
{
|
||||
char c = value_src[1];
|
||||
value_src += 2;
|
||||
switch (c)
|
||||
{
|
||||
case 'n': *value_dst++ = '\n'; break;
|
||||
case 't': *value_dst++ = '\t'; break;
|
||||
case 'v': *value_dst++ = '\v'; break;
|
||||
case 'b': *value_dst++ = '\b'; break;
|
||||
case 'f': *value_dst++ = '\f'; break;
|
||||
|
||||
case '"':
|
||||
case '\'':
|
||||
*value_dst++ = c;
|
||||
break;
|
||||
|
||||
default:
|
||||
Con_Printf("LOC_LoadFile: unrecognized escape sequence \\%c on line %d\n", c, lineno);
|
||||
*value_dst++ = c;
|
||||
break;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
if (*value_src == '\"')
|
||||
{
|
||||
trailing_quote = true;
|
||||
*value_dst = 0;
|
||||
break;
|
||||
}
|
||||
|
||||
*value_dst++ = *value_src++;
|
||||
}
|
||||
|
||||
// if not a quoted string, trim trailing whitespace
|
||||
if (!trailing_quote)
|
||||
{
|
||||
while (value_dst != value && q_isblank(value_dst[-1]))
|
||||
{
|
||||
*value_dst = 0;
|
||||
value_dst--;
|
||||
}
|
||||
}
|
||||
|
||||
if (localization.numentries == localization.maxnumentries)
|
||||
{
|
||||
// grow by 50%
|
||||
localization.maxnumentries += localization.maxnumentries >> 1;
|
||||
localization.maxnumentries = q_max(localization.maxnumentries, 32);
|
||||
localization.entries = (locentry_t*) realloc(localization.entries, sizeof(*localization.entries) * localization.maxnumentries);
|
||||
}
|
||||
|
||||
entry = &localization.entries[localization.numentries++];
|
||||
entry->key = line;
|
||||
entry->value = value;
|
||||
}
|
||||
|
||||
if (*cursor)
|
||||
*cursor++ = 0; // terminate line and advance to next
|
||||
}
|
||||
|
||||
// hash all entries
|
||||
|
||||
localization.numindices = localization.numentries * 2; // 50% load factor
|
||||
if (localization.numindices == 0)
|
||||
{
|
||||
Con_Printf("No localized strings in file '%s'\n", file);
|
||||
return;
|
||||
}
|
||||
|
||||
localization.indices = (unsigned*) realloc(localization.indices, localization.numindices * sizeof(*localization.indices));
|
||||
memset(localization.indices, 0, localization.numindices * sizeof(*localization.indices));
|
||||
|
||||
for (i = 0; i < localization.numentries; i++)
|
||||
{
|
||||
locentry_t *entry = &localization.entries[i];
|
||||
unsigned pos = COM_HashString(entry->key) % localization.numindices, end = pos;
|
||||
|
||||
for (;;)
|
||||
{
|
||||
if (!localization.indices[pos])
|
||||
{
|
||||
localization.indices[pos] = i + 1;
|
||||
break;
|
||||
}
|
||||
|
||||
++pos;
|
||||
if (pos == localization.numindices)
|
||||
pos = 0;
|
||||
|
||||
if (pos == end)
|
||||
Sys_Error("LOC_LoadFile failed");
|
||||
}
|
||||
}
|
||||
|
||||
Con_Printf("Loaded %d strings from '%s'\n", localization.numentries, file);
|
||||
}
|
||||
|
||||
/*
|
||||
================
|
||||
LOC_Init
|
||||
================
|
||||
*/
|
||||
void LOC_Init(void)
|
||||
{
|
||||
LOC_LoadFile("localization/loc_english.txt");
|
||||
}
|
||||
|
||||
/*
|
||||
================
|
||||
LOC_Shutdown
|
||||
================
|
||||
*/
|
||||
void LOC_Shutdown(void)
|
||||
{
|
||||
free(localization.indices);
|
||||
free(localization.entries);
|
||||
free(localization.text);
|
||||
}
|
||||
|
||||
/*
|
||||
================
|
||||
LOC_GetRawString
|
||||
|
||||
Returns localized string if available, or NULL otherwise
|
||||
================
|
||||
*/
|
||||
const char* LOC_GetRawString (const char *key)
|
||||
{
|
||||
unsigned pos, end;
|
||||
|
||||
if (!localization.numindices || !key || !*key || *key != '$')
|
||||
return NULL;
|
||||
key++;
|
||||
|
||||
pos = COM_HashString(key) % localization.numindices;
|
||||
end = pos;
|
||||
|
||||
do
|
||||
{
|
||||
unsigned idx = localization.indices[pos];
|
||||
locentry_t *entry;
|
||||
if (!idx)
|
||||
return NULL;
|
||||
|
||||
entry = &localization.entries[idx - 1];
|
||||
if (!Q_strcmp(entry->key, key))
|
||||
return entry->value;
|
||||
|
||||
++pos;
|
||||
if (pos == localization.numindices)
|
||||
pos = 0;
|
||||
} while (pos != end);
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/*
|
||||
================
|
||||
LOC_GetString
|
||||
|
||||
Returns localized string if available, or input string otherwise
|
||||
================
|
||||
*/
|
||||
const char* LOC_GetString (const char *key)
|
||||
{
|
||||
const char* value = LOC_GetRawString(key);
|
||||
return value ? value : key;
|
||||
}
|
||||
|
||||
/*
|
||||
================
|
||||
LOC_ParseArg
|
||||
|
||||
Returns argument index (>= 0) and advances the string if it starts with a placeholder ({} or {N}),
|
||||
otherwise returns a negative value and leaves the pointer unchanged
|
||||
================
|
||||
*/
|
||||
static int LOC_ParseArg (const char **pstr)
|
||||
{
|
||||
int arg;
|
||||
const char *start;
|
||||
const char *str = *pstr;
|
||||
|
||||
// opening brace
|
||||
if (*str != '{')
|
||||
return -1;
|
||||
start = ++str;
|
||||
|
||||
// optional index, defaulting to 0
|
||||
arg = 0;
|
||||
while (q_isdigit(*str))
|
||||
arg = arg * 10 + *str++ - '0';
|
||||
|
||||
// closing brace
|
||||
if (*str != '}')
|
||||
return -1;
|
||||
*pstr = ++str;
|
||||
|
||||
return arg;
|
||||
}
|
||||
|
||||
/*
|
||||
================
|
||||
LOC_HasPlaceholders
|
||||
================
|
||||
*/
|
||||
qboolean LOC_HasPlaceholders (const char *str)
|
||||
{
|
||||
if (!localization.numindices)
|
||||
return false;
|
||||
while (*str)
|
||||
{
|
||||
if (LOC_ParseArg(&str) >= 0)
|
||||
return true;
|
||||
str++;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/*
|
||||
================
|
||||
LOC_Format
|
||||
|
||||
Replaces placeholders (of the form {} or {N}) with the corresponding arguments
|
||||
|
||||
Returns number of written chars, excluding the NUL terminator
|
||||
If len > 0, output is always NUL-terminated
|
||||
================
|
||||
*/
|
||||
size_t LOC_Format (const char *format, const char* (*getarg_fn) (int idx, void* userdata), void* userdata, char* out, size_t len)
|
||||
{
|
||||
size_t written = 0;
|
||||
int numargs = 0;
|
||||
|
||||
if (!len)
|
||||
{
|
||||
Con_DPrintf("LOC_Format: no output space\n");
|
||||
return 0;
|
||||
}
|
||||
--len; // reserve space for the terminator
|
||||
|
||||
while (*format && written < len)
|
||||
{
|
||||
const char* insert;
|
||||
size_t space_left;
|
||||
size_t insert_len;
|
||||
int argindex = LOC_ParseArg(&format);
|
||||
|
||||
if (argindex < 0)
|
||||
{
|
||||
out[written++] = *format++;
|
||||
continue;
|
||||
}
|
||||
|
||||
insert = getarg_fn(argindex, userdata);
|
||||
space_left = len - written;
|
||||
insert_len = Q_strlen(insert);
|
||||
|
||||
if (insert_len > space_left)
|
||||
{
|
||||
Con_DPrintf("LOC_Format: overflow at argument #%d\n", numargs);
|
||||
insert_len = space_left;
|
||||
}
|
||||
|
||||
Q_memcpy(out + written, insert, insert_len);
|
||||
written += insert_len;
|
||||
}
|
||||
|
||||
if (*format)
|
||||
Con_DPrintf("LOC_Format: overflow\n");
|
||||
|
||||
out[written] = 0;
|
||||
|
||||
return written;
|
||||
}
|
||||
|
|
|
@ -190,6 +190,15 @@ void COM_CreatePath (char *path);
|
|||
char *va (const char *format, ...) FUNC_PRINTF(1,2);
|
||||
// does a varargs printf into a temp buffer
|
||||
|
||||
unsigned COM_HashString (const char *str);
|
||||
|
||||
// localization support for 2021 rerelease version:
|
||||
void LOC_Init (void);
|
||||
void LOC_Shutdown (void);
|
||||
const char* LOC_GetRawString (const char *key);
|
||||
const char* LOC_GetString (const char *key);
|
||||
qboolean LOC_HasPlaceholders (const char *str);
|
||||
size_t LOC_Format (const char *format, const char* (*getarg_fn)(int idx, void* userdata), void* userdata, char* out, size_t len);
|
||||
|
||||
//============================================================================
|
||||
|
||||
|
|
|
@ -873,6 +873,8 @@ void Host_Init (void)
|
|||
CL_Init ();
|
||||
}
|
||||
|
||||
LOC_Init (); // for 2021 rerelease support.
|
||||
|
||||
Hunk_AllocName (0, "-HOST_HUNKLEVEL-");
|
||||
host_hunklevel = Hunk_LowMark ();
|
||||
|
||||
|
@ -936,5 +938,7 @@ void Host_Shutdown(void)
|
|||
}
|
||||
|
||||
LOG_Close ();
|
||||
|
||||
LOC_Shutdown ();
|
||||
}
|
||||
|
||||
|
|
|
@ -47,21 +47,44 @@ static char *PR_GetTempString (void)
|
|||
===============================================================================
|
||||
*/
|
||||
|
||||
static const char* PF_GetStringArg(int idx, void* userdata)
|
||||
{
|
||||
if (userdata)
|
||||
idx += *(int*)userdata;
|
||||
if (idx < 0 || idx >= pr_argc)
|
||||
return "";
|
||||
return LOC_GetString(G_STRING(OFS_PARM0 + idx * 3));
|
||||
}
|
||||
|
||||
static char *PF_VarString (int first)
|
||||
{
|
||||
int i;
|
||||
static char out[1024];
|
||||
const char *format;
|
||||
size_t s;
|
||||
|
||||
out[0] = 0;
|
||||
s = 0;
|
||||
for (i = first; i < pr_argc; i++)
|
||||
|
||||
if (first >= pr_argc)
|
||||
return out;
|
||||
|
||||
format = LOC_GetString(G_STRING((OFS_PARM0 + first * 3)));
|
||||
if (LOC_HasPlaceholders(format))
|
||||
{
|
||||
s = q_strlcat(out, G_STRING((OFS_PARM0+i*3)), sizeof(out));
|
||||
if (s >= sizeof(out))
|
||||
int offset = first + 1;
|
||||
s = LOC_Format(format, PF_GetStringArg, &offset, out, sizeof(out));
|
||||
}
|
||||
else
|
||||
{
|
||||
for (i = first; i < pr_argc; i++)
|
||||
{
|
||||
Con_Warning("PF_VarString: overflow (string truncated)\n");
|
||||
return out;
|
||||
s = q_strlcat(out, LOC_GetString(G_STRING(OFS_PARM0+i*3)), sizeof(out));
|
||||
if (s >= sizeof(out))
|
||||
{
|
||||
Con_Warning("PF_VarString: overflow (string truncated)\n");
|
||||
return out;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (s > 255)
|
||||
|
@ -1540,7 +1563,7 @@ static void PF_WriteCoord (void)
|
|||
|
||||
static void PF_WriteString (void)
|
||||
{
|
||||
MSG_WriteString (WriteDest(), G_STRING(OFS_PARM1));
|
||||
MSG_WriteString (WriteDest(), LOC_GetString(G_STRING(OFS_PARM1)));
|
||||
}
|
||||
|
||||
static void PF_WriteEntity (void)
|
||||
|
|
Loading…
Reference in a new issue