#ifdef _MSC_VER #include #endif #include "language.h" #include "log.h" #include "strings.h" #include "umlaut.h" #include "assert.h" #include #include #include #include #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? */ }