2010-08-08 10:06:34 +02:00
|
|
|
/*
|
2015-01-30 22:10:29 +01:00
|
|
|
Copyright (c) 1998-2015, Enno Rehling <enno@eressea.de>
|
2014-08-24 22:54:40 +02:00
|
|
|
Katja Zedel <katze@felidae.kn-bremen.de
|
|
|
|
Christian Schlittchen <corwin@amber.kn-bremen.de>
|
2010-08-08 10:06:34 +02:00
|
|
|
|
|
|
|
Permission to use, copy, modify, and/or distribute this software for any
|
|
|
|
purpose with or without fee is hereby granted, provided that the above
|
|
|
|
copyright notice and this permission notice appear in all copies.
|
|
|
|
|
|
|
|
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
|
|
|
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
|
|
|
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
|
|
|
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
|
|
|
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
|
|
|
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
|
|
|
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
|
|
|
**/
|
|
|
|
|
|
|
|
#include <platform.h>
|
|
|
|
#include "language.h"
|
|
|
|
#include "language_struct.h"
|
|
|
|
|
|
|
|
#include "log.h"
|
2015-05-18 08:59:38 +02:00
|
|
|
#include "strings.h"
|
2010-08-08 10:06:34 +02:00
|
|
|
#include "umlaut.h"
|
|
|
|
|
|
|
|
#include <stdlib.h>
|
|
|
|
#include <stdio.h>
|
|
|
|
#include <string.h>
|
|
|
|
#include <assert.h>
|
2014-11-01 12:09:56 +01:00
|
|
|
#include <critbit.h>
|
2010-08-08 10:06:34 +02:00
|
|
|
|
|
|
|
/** importing **/
|
|
|
|
|
2011-03-07 08:02:35 +01:00
|
|
|
locale *default_locale;
|
|
|
|
locale *locales;
|
|
|
|
|
2012-05-16 22:07:28 +02:00
|
|
|
unsigned int locale_index(const locale * lang)
|
2010-08-08 10:06:34 +02:00
|
|
|
{
|
2014-08-24 22:54:40 +02:00
|
|
|
assert(lang);
|
|
|
|
return lang->index;
|
2010-08-08 10:06:34 +02:00
|
|
|
}
|
|
|
|
|
2014-06-16 07:17:08 +02:00
|
|
|
locale *get_locale(const char *name)
|
2010-08-08 10:06:34 +02:00
|
|
|
{
|
2014-08-24 22:54:40 +02:00
|
|
|
unsigned int hkey = hashstring(name);
|
|
|
|
locale *l = locales;
|
|
|
|
while (l && l->hashkey != hkey)
|
|
|
|
l = l->next;
|
|
|
|
return l;
|
2010-08-08 10:06:34 +02:00
|
|
|
}
|
|
|
|
|
2012-05-16 22:07:28 +02:00
|
|
|
static unsigned int nextlocaleindex = 0;
|
2010-08-08 10:06:34 +02:00
|
|
|
|
2014-06-16 07:17:08 +02:00
|
|
|
locale *get_or_create_locale(const char *name)
|
2010-08-08 10:06:34 +02:00
|
|
|
{
|
2014-06-16 07:17:08 +02:00
|
|
|
locale *l;
|
|
|
|
unsigned int hkey = hashstring(name);
|
|
|
|
locale **lp = &locales;
|
|
|
|
|
|
|
|
if (!locales) {
|
|
|
|
nextlocaleindex = 0;
|
2014-08-24 22:54:40 +02:00
|
|
|
}
|
|
|
|
else {
|
2014-06-16 07:17:08 +02:00
|
|
|
while (*lp && (*lp)->hashkey != hkey) lp = &(*lp)->next;
|
|
|
|
if (*lp) {
|
|
|
|
return *lp;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
*lp = l = (locale *)calloc(sizeof(locale), 1);
|
|
|
|
l->hashkey = hkey;
|
|
|
|
l->name = _strdup(name);
|
|
|
|
l->index = nextlocaleindex++;
|
|
|
|
assert(nextlocaleindex <= MAXLOCALES);
|
|
|
|
if (default_locale == NULL) default_locale = l;
|
|
|
|
return l;
|
2010-08-08 10:06:34 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
/** 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.
|
|
|
|
*/
|
2011-03-07 08:02:35 +01:00
|
|
|
void make_locales(const char *str)
|
2010-08-08 10:06:34 +02:00
|
|
|
{
|
2014-08-24 22:54:40 +02:00
|
|
|
const char *tok = str;
|
|
|
|
while (*tok) {
|
|
|
|
char zText[32];
|
|
|
|
while (*tok && *tok != ',')
|
|
|
|
++tok;
|
|
|
|
strncpy(zText, str, tok - str);
|
|
|
|
zText[tok - str] = 0;
|
|
|
|
get_or_create_locale(zText);
|
|
|
|
if (*tok) {
|
|
|
|
str = ++tok;
|
|
|
|
}
|
2010-08-08 10:06:34 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2011-03-07 08:02:35 +01:00
|
|
|
const char *locale_getstring(const locale * lang, const char *key)
|
2010-08-08 10:06:34 +02:00
|
|
|
{
|
2014-08-24 22:54:40 +02:00
|
|
|
unsigned int hkey = hashstring(key);
|
|
|
|
unsigned int id = hkey & (SMAXHASH - 1);
|
|
|
|
const struct locale_str *find;
|
2010-08-08 10:06:34 +02:00
|
|
|
|
2014-08-24 22:54:40 +02:00
|
|
|
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;
|
2010-08-08 10:06:34 +02:00
|
|
|
}
|
2014-08-24 22:54:40 +02:00
|
|
|
return NULL;
|
2010-08-08 10:06:34 +02:00
|
|
|
}
|
|
|
|
|
2015-01-08 20:55:29 +01:00
|
|
|
const char *locale_string(const locale * lang, const char *key, bool warn)
|
2010-08-08 10:06:34 +02:00
|
|
|
{
|
2014-08-24 22:54:40 +02:00
|
|
|
assert(lang);
|
|
|
|
assert(key);
|
|
|
|
|
|
|
|
if (key != NULL) {
|
|
|
|
unsigned int hkey = hashstring(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;
|
|
|
|
}
|
2014-12-31 13:17:54 +01:00
|
|
|
if (find) {
|
|
|
|
return find->str;
|
|
|
|
}
|
2015-01-08 20:55:29 +01:00
|
|
|
if (warn) {
|
|
|
|
log_warning("missing translation for \"%s\" in locale %s\n", key, lang->name);
|
|
|
|
}
|
2015-05-22 14:14:02 +02:00
|
|
|
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;
|
2014-08-24 22:54:40 +02:00
|
|
|
}
|
|
|
|
}
|
2014-12-31 13:19:44 +01:00
|
|
|
return 0;
|
2014-08-24 22:54:40 +02:00
|
|
|
}
|
2010-08-08 10:06:34 +02:00
|
|
|
|
2014-08-24 22:54:40 +02:00
|
|
|
void locale_setstring(locale * lang, const char *key, const char *value)
|
|
|
|
{
|
2010-08-08 10:06:34 +02:00
|
|
|
unsigned int hkey = hashstring(key);
|
2011-03-07 08:02:35 +01:00
|
|
|
unsigned int id = hkey & (SMAXHASH - 1);
|
|
|
|
struct locale_str *find;
|
2014-08-24 22:54:40 +02:00
|
|
|
if (!lang) {
|
|
|
|
lang = default_locale;
|
|
|
|
}
|
|
|
|
assert(lang);
|
2010-08-08 10:06:34 +02:00
|
|
|
find = lang->strings[id];
|
|
|
|
while (find) {
|
2014-08-24 22:54:40 +02:00
|
|
|
if (find->hashkey == hkey && strcmp(key, find->key) == 0)
|
|
|
|
break;
|
|
|
|
find = find->nexthash;
|
2010-08-08 10:06:34 +02:00
|
|
|
}
|
|
|
|
if (!find) {
|
2014-08-24 22:54:40 +02:00
|
|
|
find = calloc(1, sizeof(struct locale_str));
|
|
|
|
find->nexthash = lang->strings[id];
|
|
|
|
lang->strings[id] = find;
|
|
|
|
find->hashkey = hkey;
|
|
|
|
find->key = _strdup(key);
|
|
|
|
find->str = _strdup(value);
|
2010-08-08 10:06:34 +02:00
|
|
|
}
|
2014-08-24 22:54:40 +02:00
|
|
|
else {
|
|
|
|
if (strcmp(find->str, value) != 0) {
|
|
|
|
log_warning("multiple translations for key %s\n", key);
|
|
|
|
}
|
|
|
|
free(find->str);
|
|
|
|
find->str = _strdup(value);
|
2010-08-08 10:06:34 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2011-03-07 08:02:35 +01:00
|
|
|
const char *locale_name(const locale * lang)
|
2010-08-08 10:06:34 +02:00
|
|
|
{
|
2014-08-24 22:54:40 +02:00
|
|
|
return lang ? lang->name : "(null)";
|
2010-08-08 10:06:34 +02:00
|
|
|
}
|
|
|
|
|
2011-03-07 08:02:35 +01:00
|
|
|
char *mkname_buf(const char *space, const char *name, char *buffer)
|
2010-08-08 10:06:34 +02:00
|
|
|
{
|
2014-08-24 22:54:40 +02:00
|
|
|
if (space && *space) {
|
|
|
|
sprintf(buffer, "%s::%s", space, name);
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
strcpy(buffer, name);
|
|
|
|
}
|
|
|
|
return buffer;
|
2010-08-08 10:06:34 +02:00
|
|
|
}
|
|
|
|
|
2011-03-07 08:02:35 +01:00
|
|
|
const char *mkname(const char *space, const char *name)
|
2010-08-08 10:06:34 +02:00
|
|
|
{
|
2014-08-24 22:54:40 +02:00
|
|
|
static char zBuffer[128]; // FIXME: static return value
|
|
|
|
return mkname_buf(space, name, zBuffer);
|
2010-08-08 10:06:34 +02:00
|
|
|
}
|
|
|
|
|
2011-03-07 08:02:35 +01:00
|
|
|
locale *nextlocale(const struct locale * lang)
|
2010-08-08 10:06:34 +02:00
|
|
|
{
|
2014-08-24 22:54:40 +02:00
|
|
|
return lang->next;
|
2010-08-08 10:06:34 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
typedef struct lstr {
|
2014-08-24 22:54:40 +02:00
|
|
|
void * tokens[UT_MAX];
|
2010-08-08 10:06:34 +02:00
|
|
|
} lstr;
|
|
|
|
|
|
|
|
static lstr lstrs[MAXLOCALES];
|
|
|
|
|
2012-05-16 00:04:23 +02:00
|
|
|
void ** get_translations(const struct locale *lang, int index)
|
2010-08-08 10:06:34 +02:00
|
|
|
{
|
2014-08-24 22:54:40 +02:00
|
|
|
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;
|
2010-08-08 10:06:34 +02:00
|
|
|
}
|
2012-05-24 05:22:12 +02:00
|
|
|
|
2014-11-01 12:09:56 +01:00
|
|
|
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) {
|
2014-12-31 01:29:10 +01:00
|
|
|
// TODO: this will leak, because we do not know how to clean it up */
|
2014-11-01 12:09:56 +01:00
|
|
|
*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) {
|
2015-02-08 10:33:45 +01:00
|
|
|
// TODO: swap the name of s and key
|
2014-11-01 12:09:56 +01:00
|
|
|
const char * s = string_cb(i);
|
2015-01-08 20:55:29 +01:00
|
|
|
const char * key = s ? locale_string(lang, s, false) : 0;
|
2014-11-01 12:09:56 +01:00
|
|
|
key = key ? key : s;
|
|
|
|
if (key) {
|
|
|
|
struct critbit_tree ** cb = (struct critbit_tree **)tokens;
|
|
|
|
add_translation(cb, key, i);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2014-08-28 19:50:38 +02:00
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
2014-10-29 19:40:09 +01:00
|
|
|
const char *localenames[] = {
|
|
|
|
"de", "en",
|
|
|
|
NULL
|
|
|
|
};
|
|
|
|
|
|
|
|
extern void init_locale(struct locale *lang);
|
|
|
|
|
|
|
|
static int locale_init = 0;
|
|
|
|
|
|
|
|
void init_locales(void)
|
2012-05-24 05:22:12 +02:00
|
|
|
{
|
2014-10-29 19:40:09 +01:00
|
|
|
int l;
|
|
|
|
if (locale_init) return;
|
|
|
|
for (l = 0; localenames[l]; ++l) {
|
|
|
|
struct locale *lang = get_or_create_locale(localenames[l]);
|
|
|
|
init_locale(lang);
|
|
|
|
}
|
|
|
|
locale_init = 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
void reset_locales(void) {
|
|
|
|
locale_init = 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
void free_locales(void) {
|
|
|
|
locale_init = 0;
|
2014-08-24 22:54:40 +02:00
|
|
|
while (locales) {
|
|
|
|
int i;
|
|
|
|
locale * next = locales->next;
|
|
|
|
|
|
|
|
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);
|
|
|
|
}
|
|
|
|
}
|
2014-12-30 00:00:57 +01:00
|
|
|
free(locales->name);
|
2014-08-24 22:54:40 +02:00
|
|
|
free(locales);
|
|
|
|
locales = next;
|
2012-05-24 05:22:12 +02:00
|
|
|
}
|
2014-10-23 16:14:01 +02:00
|
|
|
memset(lstrs, 0, sizeof(lstrs)); // TODO: does this data need to be free'd?
|
2014-10-29 19:40:09 +01:00
|
|
|
}
|