1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266
|
/*
* Crossfire -- cooperative multi-player graphical RPG and adventure game
*
* Copyright (c) 1999-2014 Mark Wedel and the Crossfire Development Team
* Copyright (c) 1992 Frank Tore Johansen
*
* Crossfire is free software and comes with ABSOLUTELY NO WARRANTY. You are
* welcome to redistribute it under certain conditions. For details, please
* see COPYING and LICENSE.
*
* The authors can be reached via e-mail at <crossfire@metalforge.org>.
*/
#include "global.h"
#include <errno.h>
#include <stdlib.h>
#include <string.h>
/**
* One message.
*/
typedef struct i18n_message {
sstring code; /**< Message code, usually the English version. */
sstring message; /**< Message to display. */
} i18n_message;
/**
* One available language.
*/
typedef struct i18n_file {
sstring code; /**< Language code, "message." extension. */
sstring name; /**< Language's name, in its native version. */
int count; /**< How many items in messages. */
struct i18n_message *messages; /**< Available messages for this language. */
} i18n_file;
/** Number of defined languages. */
static int i18n_count = 0;
/** Defined languages. */
static struct i18n_file *i18n_files = NULL;
/** Index of "English" in the i18nfiles array. */
static int i18n_default = -1;
static int i18n_message_compare_code(const i18n_message *a, const i18n_message *b) {
return strcmp(a->code, b->code);
}
/**
* Returns the i18n language index associated with the given object.
* This only has a meaning for players.
* @param op The player object to get the language of
* @return The language numerical code. If none is associated, get_language returns 0
*/
int get_language(object *op) {
if (!op->contr)
return 0;
if (op->contr->language < 0 || op->contr->language >= i18n_count)
return 0;
return op->contr->language;
}
/**
* Translate a message in the appropriate language.
* @param who who to translate for.
* @param code string to translate, usually the English version.
* @return translated message, or code if not found or who's language is invalid.
*/
const char *i18n(const object *who, const char *code) {
i18n_message search, *found;
if (!who || !who->contr)
return code;
if (who->contr->language < 0 || who->contr->language >= i18n_count)
return code;
search.code = add_string(code);
found = bsearch(&search, i18n_files[who->contr->language].messages, i18n_files[who->contr->language].count, sizeof(i18n_message), (int (*)(const void *, const void *))i18n_message_compare_code);
free_string(search.code);
if (found)
return found->message;
return code;
}
/**
* Attempt to find the identifier of a language from its code.
* @param code language code.
* @return index, -1 if not found.
*/
int i18n_find_language_by_code(const char *code) {
int index;
for (index = 0; index < i18n_count; index++) {
if (strcmp(code, i18n_files[index].code) == 0)
return index;
}
return -1;
}
/**
* Find the identifier of a language from its code.
* @param code language's code.
* @return language's code, or the default language if code is invalid.
*/
int i18n_get_language_by_code(const char *code) {
int try = i18n_find_language_by_code(code);
if (try != -1)
return try;
return i18n_default;
}
/**
* Return the code of a specified language.
* @param language identifier of the language.
* @return language's code, or default language's code if identifier is invalid.
*/
sstring i18n_get_language_code(int language) {
if (language < 0 || language >= i18n_count)
return i18n_files[i18n_default].code;
return i18n_files[language].code;
}
/**
* List all languages for who.
* @param who who to display languages for.
*/
void i18n_list_languages(object *who) {
int index;
for (index = 0; index < i18n_count; index++) {
draw_ext_info_format(NDI_UNIQUE, 0, who, MSG_TYPE_COMMAND, MSG_SUBTYPE_NONE,
"[fixed]%s: %s",
i18n_files[index].code,
i18n_files[index].name
);
}
}
/**
* Replaces '\n' by a newline char.
*
* Since we are replacing 2 chars by 1, no overflow should happen.
*
* @param line
* text to replace into.
*/
static void convert_newline(char *line) {
char *next;
char buf[MAX_BUF];
while ((next = strstr(line, "\\n")) != NULL) {
*next = '\n';
*(next+1) = '\0';
snprintf(buf, MAX_BUF, "%s%s", line, next+2);
strcpy(line, buf);
}
}
/**
* Initializes the i18n subsystem.
* Will load all found strings.
* If there is an error, calls fatal().
*/
void i18n_init(void) {
char dirname[MAX_BUF], filename[MAX_BUF*2], line[HUGE_BUF];
FILE *fp;
char *token;
DIR *dir;
struct dirent *file;
i18n_message code, *found;
snprintf(dirname, sizeof(dirname), "%s/i18n/", settings.datadir);
dir = opendir(dirname);
if (dir == NULL) {
LOG(llevError, "i18n: couldn't open %s\n", dirname);
fatal(SEE_LAST_ERROR);
}
code.code = add_string("LN");
while ((file = readdir(dir)) != NULL) {
if (strncmp(file->d_name, "messages.", 9) != 0)
continue;
snprintf(filename, sizeof(filename), "%s%s", dirname, file->d_name);
if ((fp = fopen(filename, "r")) == NULL) {
LOG(llevError, "i18n: couldn't open %s\n",
filename, strerror(errno));
fatal(SEE_LAST_ERROR);
}
i18n_files = realloc(i18n_files, (i18n_count + 1) * sizeof(i18n_file));
i18n_files[i18n_count].code = add_string(file->d_name + 9);
i18n_files[i18n_count].count = 0;
i18n_files[i18n_count].messages = NULL;
while (fgets(line, MAX_BUF, fp)) {
if (line[0] != '#') {
line[strlen(line)-1] = '\0'; /* erase the final newline that messes things. */
i18n_files[i18n_count].messages = realloc(i18n_files[i18n_count].messages, (i18n_files[i18n_count].count + 1) * sizeof(i18n_message));
token = strtok(line, "|");
convert_newline(token);
i18n_files[i18n_count].messages[i18n_files[i18n_count].count].code = add_string(token);
token = strtok(NULL, "|");
if (token != NULL) {
convert_newline(token);
i18n_files[i18n_count].messages[i18n_files[i18n_count].count].message = add_string(token);
} else {
i18n_files[i18n_count].messages[i18n_files[i18n_count].count].message = add_refcount(i18n_files[i18n_count].messages[i18n_files[i18n_count].count].code);
}
i18n_files[i18n_count].count++;
}
}
fclose(fp);
qsort(i18n_files[i18n_count].messages, i18n_files[i18n_count].count, sizeof(i18n_message), (int (*)(const void *, const void *))i18n_message_compare_code);
found = bsearch(&code, i18n_files[i18n_count].messages, i18n_files[i18n_count].count, sizeof(i18n_message), (int (*)(const void *, const void *))i18n_message_compare_code);
if (found == NULL) {
LOG(llevError, "i18n: no language set in %s\n", filename);
fatal(SEE_LAST_ERROR);
}
i18n_files[i18n_count].name = found->message;
LOG(llevDebug, "i18n: %d strings for %s\n",
i18n_files[i18n_count].count, found->message);
if (strcmp(i18n_files[i18n_count].code, "en") == 0)
i18n_default = i18n_count;
i18n_count++;
}
closedir(dir);
free_string(code.code);
if (i18n_default == -1) {
LOG(llevError, "i18n: couldn't find default language (en)\n");
fatal(SEE_LAST_ERROR);
}
}
/**
* Clears all i18n-related data.
*/
void i18n_free(void) {
int file, message;
for (file = 0; file < i18n_count; file++) {
free_string(i18n_files[file].code); /* name is a copy of a message */
for (message = 0; message < i18n_files[file].count; message++) {
free_string(i18n_files[file].messages[message].code);
free_string(i18n_files[file].messages[message].message);
}
free(i18n_files[file].messages);
}
free(i18n_files);
i18n_files = NULL;
i18n_count = 0;
}
|