server/src/util/language.c
2019-10-07 22:01:02 +02:00

408 lines
10 KiB
C

#ifdef _MSC_VER
#include <platform.h>
#endif
#include "language.h"
#include "log.h"
#include "strings.h"
#include "umlaut.h"
#include "assert.h"
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <critbit.h>
#define SMAXHASH 2048
typedef struct locale_str {
unsigned int hashkey;
struct locale_str *nexthash;
char *str;
char *key;
} locale_str;
typedef struct locale {
char *name;
unsigned int index;
struct locale *next;
unsigned int hashkey;
struct locale_str *strings[SMAXHASH];
} locale;
locale *default_locale;
locale *locales;
unsigned int locale_index(const locale * lang)
{
assert(lang);
return lang->index;
}
locale *get_locale(const char *name)
{
unsigned int hkey = str_hash(name);
locale *l = locales;
while (l && l->hashkey != hkey)
l = l->next;
return l;
}
static unsigned int nextlocaleindex = 0;
locale *get_or_create_locale(const char *name)
{
locale *l;
unsigned int hkey = str_hash(name);
locale **lp = &locales;
if (!locales) {
nextlocaleindex = 0;
}
else {
while (*lp && (*lp)->hashkey != hkey) lp = &(*lp)->next;
if (*lp) {
return *lp;
}
}
*lp = l = (locale *)calloc(1, sizeof(locale));
assert_alloc(l);
l->hashkey = hkey;
l->name = str_strdup(name);
l->index = nextlocaleindex++;
assert(nextlocaleindex <= MAXLOCALES);
if (default_locale == NULL) default_locale = l;
return l;
}
/** creates a list of locales
* This function takes a comma-delimited list of locale-names and creates
* the locales using the make_locale function (useful for ini-files).
* For maximum performance, locales should be created in order of popularity.
*/
void make_locales(const char *str)
{
const char *tok = str;
while (tok) {
char zText[16];
size_t len;
tok = strchr(str, ',');
if (tok) {
len = tok - str;
assert(sizeof(zText) > len);
memcpy(zText, str, len);
str = tok + 1;
}
else {
len = strlen(str);
memcpy(zText, str, len);
}
zText[len] = 0;
get_or_create_locale(zText);
}
}
const char *locale_getstring(const locale * lang, const char *key)
{
unsigned int hkey = str_hash(key);
unsigned int id = hkey & (SMAXHASH - 1);
const struct locale_str *find;
assert(lang);
if (key == NULL || *key == 0)
return NULL;
find = lang->strings[id];
while (find) {
if (find->hashkey == hkey) {
if (find->nexthash == NULL) {
/* if this is the only entry with this hash, fine. */
assert(strcmp(key, find->key) == 0);
return find->str;
}
if (strcmp(key, find->key) == 0) {
return find->str;
}
}
find = find->nexthash;
}
return NULL;
}
const char *locale_plural(const struct locale *lang, const char *key, int n, bool warn) {
assert(lang);
assert(key);
if (n != 1) {
char plural[32];
snprintf(plural, 32, "%s_p", key);
plural[31] = '\0';
return locale_string(lang, plural, warn);
}
return locale_string(lang, key, warn);
}
const char *locale_string(const locale * lang, const char *key, bool warn)
{
assert(lang);
assert(key);
if (key != NULL) {
unsigned int hkey = str_hash(key);
unsigned int id = hkey & (SMAXHASH - 1);
struct locale_str *find;
if (*key == 0) return 0;
find = lang->strings[id];
while (find) {
if (find->hashkey == hkey) {
if (!find->nexthash) {
/* if this is the only entry with this hash, fine. */
assert(strcmp(key, find->key) == 0);
break;
}
if (strcmp(key, find->key) == 0)
break;
}
find = find->nexthash;
}
if (find) {
return find->str;
}
if (warn) {
log_warning("missing translation for \"%s\" in locale %s\n", key, lang->name);
}
if (default_locale && lang != default_locale) {
const char * value = locale_string(default_locale, key, warn);
if (value) {
/* TODO: evil side-effects for a const function */
locale_setstring(get_or_create_locale(lang->name), key, value);
}
return value;
}
}
return NULL;
}
void locale_setstring(locale * lang, const char *key, const char *value)
{
unsigned int hkey = str_hash(key);
unsigned int id = hkey & (SMAXHASH - 1);
struct locale_str *find;
if (!lang) {
lang = default_locale;
}
assert(lang);
find = lang->strings[id];
while (find) {
if (find->hashkey == hkey && strcmp(key, find->key) == 0)
break;
find = find->nexthash;
}
if (!find) {
find = calloc(1, sizeof(struct locale_str));
if (!find) abort();
find->nexthash = lang->strings[id];
lang->strings[id] = find;
find->hashkey = hkey;
find->key = str_strdup(key);
find->str = str_strdup(value);
}
else {
if (strcmp(find->str, value) != 0) {
log_warning("multiple translations for key %s\n", key);
}
free(find->str);
find->str = str_strdup(value);
}
}
static const char *escape_str(const char *in, FILE *F) {
while (*in) {
char buffer[64];
size_t len = 0;
char *out = buffer;
while (sizeof(buffer) > len + 2) {
if (*in == '\0') break;
if (strchr("\\\"", *in) != NULL) {
*out++ = '\\';
++len;
}
*out++ = *in++;
++len;
}
if (len > 0) {
fwrite(buffer, 1, len, F);
}
}
return in;
}
void po_write_msg(FILE *F, const char *id, const char *str, const char *ctxt) {
if (ctxt) {
fputs("msgctxt \"", F);
escape_str(ctxt, F);
fputs("\"\nmsgid \"", F);
escape_str(id, F);
}
else {
fputs("msgid \"", F);
escape_str(id, F);
}
fputs("\"\nmsgstr \"", F);
escape_str(str, F);
fputs("\"\n\n", F);
}
const char *locale_name(const locale * lang)
{
return lang ? lang->name : "(null)";
}
char *mkname_buf(const char *space, const char *name, char *buffer)
{
if (space && *space) {
sprintf(buffer, "%s::%s", space, name);
}
else {
strcpy(buffer, name);
}
return buffer;
}
const char *mkname(const char *space, const char *name)
{
static char zBuffer[128]; /* FIXME: static return value */
return mkname_buf(space, name, zBuffer);
}
locale *nextlocale(const struct locale * lang)
{
return lang->next;
}
typedef struct lstr {
void * tokens[UT_MAX];
} lstr;
static lstr lstrs[MAXLOCALES];
void ** get_translations(const struct locale *lang, int index)
{
assert(lang);
assert(lang->index < MAXLOCALES
|| !"you have to increase MAXLOCALES and recompile");
if (lang->index < MAXLOCALES) {
return lstrs[lang->index].tokens + index;
}
return lstrs[0].tokens + index;
}
void add_translation(struct critbit_tree **cbp, const char *key, int i) {
char buffer[256];
char * str = transliterate(buffer, sizeof(buffer) - sizeof(int), key);
struct critbit_tree * cb = *cbp;
if (str) {
size_t len = strlen(str);
if (!cb) {
/* TODO: this will leak, because we do not know how to clean it up */
*cbp = cb = (struct critbit_tree *)calloc(1, sizeof(struct critbit_tree));
}
len = cb_new_kv(str, len, &i, sizeof(int), buffer);
cb_insert(cb, buffer, len);
}
else {
log_error("could not transliterate '%s'\n", key);
}
}
void init_translations(const struct locale *lang, int ut, const char * (*string_cb)(int i), int maxstrings)
{
void **tokens;
int i;
assert(string_cb);
assert(maxstrings > 0);
tokens = get_translations(lang, ut);
for (i = 0; i != maxstrings; ++i) {
/* TODO: swap the name of s and key */
const char * s = string_cb(i);
if (s) {
const char * key = locale_string(lang, s, false);
if (key) {
struct critbit_tree ** cb = (struct critbit_tree **)tokens;
add_translation(cb, key, i);
}
else {
log_warning("no translation for %s in locale %s", s, lang->name);
}
}
}
}
void *get_translation(const struct locale *lang, const char *str, int index) {
void **tokens = get_translations(lang, index);
variant var;
if (findtoken(*tokens, str, &var) == E_TOK_SUCCESS) {
return var.v;
}
return NULL;
}
const char *localenames[] = {
"de", "en",
NULL
};
static int locale_init = 0;
void init_locales(locale_handler init)
{
locale * lang;
if (locale_init) return;
assert(locales);
for (lang = locales; lang; lang = nextlocale(lang)) {
init(lang);
}
locale_init = 1;
}
void reset_locales(void) {
locale_init = 0;
}
void free_locales(void) {
locale_init = 0;
while (locales) {
int i, index = locales->index;
locale * next = locales->next;
for (i = UT_PARAMS; i != UT_RACES; ++i) {
struct critbit_tree ** cb = (struct critbit_tree **)get_translations(locales, i);
if (*cb) {
/* TODO: this crashes? */
cb_clear(*cb);
free(*cb);
}
}
for (i = UT_RACES; i != UT_MAX; ++i) {
void *tokens = lstrs[index].tokens[i];
if (tokens) {
freetokens(tokens);
}
}
for (i = 0; i != SMAXHASH; ++i) {
while (locales->strings[i]) {
struct locale_str * strings = locales->strings[i];
free(strings->key);
free(strings->str);
locales->strings[i] = strings->nexthash;
free(strings);
}
}
free(locales->name);
free(locales);
locales = next;
}
memset(lstrs, 0, sizeof(lstrs)); /* TODO: does this data need to be free'd? */
}