Add basic localized strings support for 2021 re-release

See https://github.com/Novum/vkQuake/pull/345
This commit is contained in:
Andrei Drexler 2021-08-29 17:11:28 +03:00 committed by Ozkan Sezer
parent 2313298d14
commit 83af8d060f
4 changed files with 463 additions and 6 deletions

View file

@ -2463,3 +2463,424 @@ long FS_filelength (fshandle_t *fh)
return fh->length; 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;
}

View file

@ -190,6 +190,15 @@ void COM_CreatePath (char *path);
char *va (const char *format, ...) FUNC_PRINTF(1,2); char *va (const char *format, ...) FUNC_PRINTF(1,2);
// does a varargs printf into a temp buffer // 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);
//============================================================================ //============================================================================

View file

@ -873,6 +873,8 @@ void Host_Init (void)
CL_Init (); CL_Init ();
} }
LOC_Init (); // for 2021 rerelease support.
Hunk_AllocName (0, "-HOST_HUNKLEVEL-"); Hunk_AllocName (0, "-HOST_HUNKLEVEL-");
host_hunklevel = Hunk_LowMark (); host_hunklevel = Hunk_LowMark ();
@ -936,5 +938,7 @@ void Host_Shutdown(void)
} }
LOG_Close (); LOG_Close ();
LOC_Shutdown ();
} }

View file

@ -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) static char *PF_VarString (int first)
{ {
int i; int i;
static char out[1024]; static char out[1024];
const char *format;
size_t s; size_t s;
out[0] = 0; out[0] = 0;
s = 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)); int offset = first + 1;
if (s >= sizeof(out)) 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"); s = q_strlcat(out, LOC_GetString(G_STRING(OFS_PARM0+i*3)), sizeof(out));
return out; if (s >= sizeof(out))
{
Con_Warning("PF_VarString: overflow (string truncated)\n");
return out;
}
} }
} }
if (s > 255) if (s > 255)
@ -1540,7 +1563,7 @@ static void PF_WriteCoord (void)
static void PF_WriteString (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) static void PF_WriteEntity (void)