UFO: Alien Invasion
 All Data Structures Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Macros Modules Pages
cl_language.cpp
Go to the documentation of this file.
1 
6 /*
7 All original material Copyright (C) 2002-2020 UFO: Alien Invasion.
8 
9 This program is free software; you can redistribute it and/or
10 modify it under the terms of the GNU General Public License
11 as published by the Free Software Foundation; either version 2
12 of the License, or (at your option) any later version.
13 
14 This program is distributed in the hope that it will be useful,
15 but WITHOUT ANY WARRANTY; without even the implied warranty of
16 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
17 
18 See the GNU General Public License for more details.
19 
20 You should have received a copy of the GNU General Public License
21 along with this program; if not, write to the Free Software
22 Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
23 
24 */
25 
26 #include "client.h"
27 #include "cl_language.h"
28 #include "../shared/parse.h"
29 #include "../ports/system.h"
30 
31 #include "ui/ui_main.h"
32 #include "ui/ui_font.h"
34 
37 
38 #define MAX_MSGIDS 512
39 
43 typedef struct msgid_s {
44  const char* id;
45  const char* text;
46  struct msgid_s* hash_next;
47 } msgid_t;
48 
50 static int numMsgIDs;
51 #define MAX_MSGIDHASH 256
53 
54 #define MSGIDSIZE 65536
55 static char* msgIDText;
56 
57 static void CL_ParseMessageID (const char* name, const char** text)
58 {
59  /* get it's body */
60  const char* token = Com_Parse(text);
61  if (!*text || *token != '{') {
62  Com_Printf("CL_ParseMessageID: msgid \"%s\" without body ignored\n", name);
63  return;
64  }
65 
66  /* search for game types with same name */
67  int i;
68  for (i = 0; i < numMsgIDs; i++)
69  if (Q_streq(token, msgIDs[i].id))
70  break;
71 
72  if (i == numMsgIDs) {
73  msgid_t* msgid = &msgIDs[numMsgIDs++];
74 
75  if (numMsgIDs >= MAX_MSGIDS)
76  Sys_Error("CL_ParseMessageID: MAX_MSGIDS exceeded");
77 
78  OBJZERO(*msgid);
79  msgid->id = Mem_PoolStrDup(name, cl_msgidPool, 0);
80  const unsigned int hash = Com_HashKey(msgid->id, MAX_MSGIDHASH);
81  HASH_Add(msgIDHash, msgid, hash);
82 
83  do {
84  const char* errhead = "CL_ParseMessageID: unexpected end of file (msgid ";
85  token = Com_EParse(text, errhead, name);
86  if (!*text)
87  break;
88  if (*token == '}')
89  break;
90  if (Q_streq(token, "text")) {
91  /* found a definition */
92  token = Com_EParse(text, errhead, name, msgIDText, MSGIDSIZE);
93  if (!*text)
94  break;
95  if (token[0] == '_')
96  token++;
97  if (token[0] != '\0')
98  msgid->text = _(token);
99  else
100  msgid->text = token;
101  if (msgid->text == token) {
102  msgid->text = Mem_PoolStrDup(token, cl_msgidPool, 0);
103  Com_Printf("no translation for %s\n", msgid->id);
104  }
105  }
106  } while (*text);
107  } else {
108  Com_Printf("CL_ParseMessageID: msgid \"%s\" with same already exists - ignore the second one\n", name);
109  Com_SkipBlock(text);
110  }
111 }
112 
113 static const char* CL_GetMessageID (const char* id)
114 {
115  const unsigned int hash = Com_HashKey(id, MAX_MSGIDHASH);
116  for (msgid_t** anchor = &msgIDHash[hash]; *anchor; anchor = &(*anchor)->hash_next) {
117  if (Q_streq(id, (*anchor)->id))
118  return (*anchor)->text;
119  }
120  return id;
121 }
122 
123 const char* CL_Translate (const char* t)
124 {
125  if (t[0] == '_') {
126  if (t[1] != '\0')
127  t = _(++t);
128  } else {
129  const char* msgid = Q_strstart(t, "*msgid:");
130  if (msgid != nullptr)
131  t = CL_GetMessageID(msgid);
132  }
133 
134  return t;
135 }
136 
138 {
139  const char* type, *name;
140 
141  numMsgIDs = 0;
142  OBJZERO(msgIDHash);
143 
144  if (cl_msgidPool != nullptr) {
145  Mem_FreePool(cl_msgidPool);
146  } else {
147  cl_msgidPool = Mem_CreatePool("msgids");
148  }
149  msgIDText = Mem_PoolAllocTypeN(char, MSGIDSIZE, cl_msgidPool);
150 
151  Com_Printf("\n----------- parse msgids -----------\n");
152 
153  Com_Printf("%i msgid files\n", FS_BuildFileList("ufos/msgid/*.ufo"));
154  const char* text = nullptr;
155 
156  FS_NextScriptHeader(nullptr, nullptr, nullptr);
157 
158  while ((type = FS_NextScriptHeader("ufos/msgid/*.ufo", &name, &text)) != nullptr) {
159  if (Q_streq(type, "msgid"))
160  CL_ParseMessageID(name, &text);
161  }
162 }
163 
167 typedef struct localeMapping_s {
171 
176 typedef struct language_s {
177  const char* localeID;
178  const char* localeString;
179  const char* nativeString;
181  struct language_s* next;
182 } language_t;
183 
185 static int languageCount;
191 static const char* CL_GetLocaleID (const char* fullLocale)
192 {
193  language_t* language = languageList;
194  while (language) {
195  localeMapping_t* mapping = language->localeMapping;
196  while (mapping) {
197  if (Q_streq(fullLocale, mapping->localeMapping))
198  return language->localeID;
199  mapping = mapping->next;
200  }
201  language = language->next;
202  }
203  Com_DPrintf(DEBUG_CLIENT, "CL_GetLocaleID: Could not find your system locale '%s'. "
204  "Add it to the languages script file and send a patch please.\n", fullLocale);
205  return nullptr;
206 }
207 
211 void CL_ParseLanguages (const char* name, const char** text)
212 {
213  const char* errhead = "CL_ParseLanguages: unexpected end of file (language ";
214 
215  if (!*text) {
216  Com_Printf("CL_ParseLanguages: language without body ignored (%s)\n", name);
217  return;
218  }
219 
220  const char* token = Com_EParse(text, errhead, name);
221  if (!*text || *token != '{') {
222  Com_Printf("CL_ParseLanguages: language without body ignored (%s)\n", name);
223  return;
224  }
225 
227  language->localeID = Mem_PoolStrDup(name, cl_genericPool, 0);
228  language->localeString = "";
229  language->nativeString = "";
230  language->localeMapping = nullptr;
231 
232  do {
233  /* get the name type */
234  token = Com_EParse(text, errhead, name);
235  if (!*text || *token == '}')
236  break;
237  /* inner locale id definition */
238  if (Q_streq(token, "code")) {
239  linkedList_t* list;
240  if (!Com_ParseList(text, &list)) {
241  Com_Error(ERR_DROP, "CL_ParseLanguages: error while reading language codes \"%s\"", name);
242  }
243  for (linkedList_t* element = list; element != nullptr; element = element->next) {
245  mapping->localeMapping = Mem_PoolStrDup((char*)element->data, cl_genericPool, 0);
246  /* link it in */
247  mapping->next = language->localeMapping;
248  language->localeMapping = mapping;
249  }
250  LIST_Delete(&list);
251  } else if (Q_streq(token, "name")) {
252  token = Com_EParse(text, errhead, name);
253  if (!*text || *token == '}')
254  Com_Error(ERR_FATAL, "CL_ParseLanguages: Name expected for language \"%s\".\n", name);
255  if (*token != '_') {
256  Com_Printf("CL_ParseLanguages: language: '%s' - not marked translatable (%s)\n", name, token);
257  }
258  language->localeString = Mem_PoolStrDup(token, cl_genericPool, 0);
259  } else if (Q_streq(token, "native")) {
260  token = Com_EParse(text, errhead, name);
261  if (!*text || *token == '}')
262  Com_Error(ERR_FATAL, "CL_ParseLanguages: Native expected for language \"%s\".\n", name);
263  language->nativeString = Mem_PoolStrDup(token, cl_genericPool, 0);
264  }
265  } while (*text);
266 
267  language->next = languageList;
268  languageList = language;
269  languageCount++;
270 }
271 
277 static bool CL_LanguageTest (const char* localeID)
278 {
279 #ifndef _WIN32
280  int i;
281  language_t* language;
282 #endif
283  char languagePath[MAX_OSPATH];
284 
285  assert(localeID);
286 
287  /* Find the proper *.mo file. */
288  if (fs_i18ndir->string[0] != '\0')
289  Q_strncpyz(languagePath, fs_i18ndir->string, sizeof(languagePath));
290  else
291 #ifdef LOCALEDIR
292  Com_sprintf(languagePath, sizeof(languagePath), LOCALEDIR);
293 #else
294  Com_sprintf(languagePath, sizeof(languagePath), "%s/" BASEDIRNAME "/i18n/", FS_GetCwd());
295 #endif
296  Com_DPrintf(DEBUG_CLIENT, "CL_LanguageTest: using mo files from '%s'\n", languagePath);
297  Q_strcat(languagePath, sizeof(languagePath), "%s/LC_MESSAGES/ufoai.mo", localeID);
298 
299  /* No *.mo file -> no language. */
300  if (!FS_FileExists("%s", languagePath)) {
301  Com_DPrintf(DEBUG_CLIENT, "CL_LanguageTest: locale '%s' not found.\n", localeID);
302  return false;
303  }
304 
305 #ifdef _WIN32
306  if (Sys_Setenv("LANGUAGE=%s", localeID) == 0) {
307  Com_DPrintf(DEBUG_CLIENT, "CL_LanguageTest: locale '%s' found.\n", localeID);
308  return true;
309  }
310 #else
311  for (i = 0, language = languageList; i < languageCount; language = language->next, i++) {
312  if (Q_streq(localeID, language->localeID))
313  break;
314  }
315  if (i == languageCount) {
316  Com_DPrintf(DEBUG_CLIENT, "CL_LanguageTest: Could not find locale with id '%s'\n", localeID);
317  return false;
318  }
319 
320  localeMapping_t* mapping = language->localeMapping;
321  if (!mapping) {
322  Com_DPrintf(DEBUG_CLIENT, "No locale mappings for locale with id '%s'\n", localeID);
323  return false;
324  }
325  /* Cycle through all mappings, but stop at first locale possible to set. */
326  do {
327  /* setlocale() will return nullptr if no setting possible. */
328  if (setlocale(LC_MESSAGES, mapping->localeMapping)) {
329  Com_DPrintf(DEBUG_CLIENT, "CL_LanguageTest: language '%s' with locale '%s' found.\n", localeID, mapping->localeMapping);
330  return true;
331  } else
332  Com_DPrintf(DEBUG_CLIENT, "CL_LanguageTest: language '%s' with locale '%s' not found on your system.\n", localeID, mapping->localeMapping);
333  mapping = mapping->next;
334  } while (mapping);
335  Com_DPrintf(DEBUG_CLIENT, "CL_LanguageTest: not possible to use language '%s'.\n", localeID);
336 #endif
337 
338  return false;
339 }
340 
342 {
343  languageCount = 0;
344  languageList = nullptr;
345  Mem_DeletePool(cl_msgidPool);
346  cl_msgidPool = nullptr;
347  msgIDText = nullptr;
348  numMsgIDs = 0;
349  OBJZERO(msgIDHash);
350 }
351 
353 {
354  uiNode_t* languageOption = nullptr;
355  language_t* language = languageList;
356  while (language) {
357  const bool available = Q_streq(language->localeID, "none") || CL_LanguageTest(language->localeID);
358  uiNode_t* option = UI_AddOption(&languageOption, "", language->nativeString, language->localeID);
359  option->disabled = !available;
360  language = language->next;
361  }
362 
363  /* sort the list, and register it to the menu */
364  UI_SortOptions(&languageOption);
365  UI_RegisterOption(OPTION_LANGUAGES, languageOption);
366 
367  /* Set to the locale remembered previously. */
369 }
370 
376 void CL_LanguageInit (void)
377 {
378  fs_i18ndir = Cvar_Get("fs_i18ndir", "", 0, "System path to language files");
379 
380  char systemLanguage[MAX_VAR] = "";
381  if (Q_strvalid(s_language->string)) {
382  Com_Printf("CL_LanguageInit: language settings are stored in configuration: %s\n", s_language->string);
383  Q_strncpyz(systemLanguage, s_language->string, sizeof(systemLanguage));
384  } else {
385  const char* currentLocale = Sys_GetLocale();
386  if (currentLocale) {
387  const char* localeID = CL_GetLocaleID(currentLocale);
388  if (localeID)
389  Q_strncpyz(systemLanguage, localeID, sizeof(systemLanguage));
390  }
391  }
392 
393  Com_DPrintf(DEBUG_CLIENT, "CL_LanguageInit: system language is: '%s'\n", systemLanguage);
394 }
395 
399 static void CL_NewLanguage (void)
400 {
401  R_FontShutdown();
402  R_FontInit();
403  UI_InitFonts();
406 }
407 
413 bool CL_LanguageTryToSet (const char* localeID)
414 {
415  int i;
416  language_t* language;
417 
418  assert(localeID);
419 
420  /* in case of an error we really don't want a flooded console */
421  s_language->modified = false;
422 
423  for (i = 0, language = languageList; i < languageCount; language = language->next, i++) {
424  if (Q_streq(localeID, language->localeID))
425  break;
426  }
427 
428  if (i == languageCount) {
429  Com_Printf("Could not find locale with id '%s'\n", localeID);
430  return false;
431  }
432 
433  localeMapping_t* mapping = language->localeMapping;
434  if (!mapping) {
435  Com_Printf("No locale mappings for locale with id '%s'\n", localeID);
436  return false;
437  }
438 
439  Cvar_Set("s_language", "%s", localeID);
440  s_language->modified = false;
441 
442  do {
443  Com_DPrintf(DEBUG_CLIENT, "CL_LanguageTryToSet: %s (%s)\n", mapping->localeMapping, localeID);
444  if (Sys_SetLocale(mapping->localeMapping)) {
445  CL_NewLanguage();
446  return true;
447  }
448  mapping = mapping->next;
449  } while (mapping);
450 
451 #ifndef _WIN32
452  Com_DPrintf(DEBUG_CLIENT, "CL_LanguageTryToSet: Finally try: '%s'\n", localeID);
453  Sys_SetLocale(localeID);
454  CL_NewLanguage();
455 #endif
456 
457  return false;
458 }
#define BASEDIRNAME
Definition: filesys.h:34
static char * msgIDText
Definition: cl_language.cpp:55
static cvar_t * fs_i18ndir
Definition: cl_language.cpp:35
static language_t * languageList
void Sys_Error(const char *error,...)
Definition: g_main.cpp:421
#define MAX_MSGIDHASH
Definition: cl_language.cpp:51
const char * text
Definition: cl_language.cpp:45
QGL_EXTERN GLint GLenum type
Definition: r_gl.h:94
static void CL_ParseMessageID(const char *name, const char **text)
Definition: cl_language.cpp:57
struct language_s * next
void UI_SortOptions(uiNode_t **first)
Sort options by alphabet.
Definition: ui_data.cpp:273
int Sys_Setenv(const char *name, const char *value)
set/unset environment variables (empty value removes it)
Definition: unix_shared.cpp:60
This is a cvar definition. Cvars can be user modified and used in our menus e.g.
Definition: cvar.h:71
#define _(String)
Definition: cl_shared.h:43
void CL_ParseMessageIDs(void)
bool Com_sprintf(char *dest, size_t size, const char *fmt,...)
copies formatted string with buffer-size checking
Definition: shared.cpp:494
bool FS_FileExists(const char *filename,...)
Checks whether a file exists (not in virtual filesystem)
Definition: files.cpp:1581
struct localeMapping_s localeMapping_t
List of all mappings for a locale.
void CL_LanguageInit(void)
Fills the options language menu node with the parsed language mappings.
void CL_LanguageShutdown(void)
const char * Sys_SetLocale(const char *localeID)
Definition: win_shared.cpp:147
struct msgid_s msgid_t
void R_FontSetTruncationMarker(const char *marker)
Definition: r_font.cpp:112
static bool CL_LanguageTest(const char *localeID)
Test given language by trying to set locale.
Struct that reflects parsed language definitions from our script files.
#define MAX_OSPATH
Definition: filesys.h:44
void Com_Printf(const char *const fmt,...)
Definition: common.cpp:386
static const char * CL_GetMessageID(const char *id)
int FS_BuildFileList(const char *fileList)
Build a filelist.
Definition: files.cpp:960
void LIST_Delete(linkedList_t **list)
Definition: list.cpp:195
struct msgid_s * hash_next
Definition: cl_language.cpp:46
#define ERR_FATAL
Definition: common.h:210
memPool_t * cl_genericPool
Definition: cl_main.cpp:86
static memPool_t * cl_msgidPool
Definition: cl_language.cpp:36
#define Q_strvalid(string)
Definition: shared.h:141
void Com_Error(int code, const char *fmt,...)
Definition: common.cpp:417
static const char * CL_GetLocaleID(const char *fullLocale)
Searches the locale script id with the given locale string.
void CL_LanguageInitMenu(void)
GLuint * id
Definition: r_gl.h:149
void Q_strncpyz(char *dest, const char *src, size_t destsize)
Safe strncpy that ensures a trailing zero.
Definition: shared.cpp:457
static msgid_t msgIDs[MAX_MSGIDS]
Definition: cl_language.cpp:49
#define ERR_DROP
Definition: common.h:211
#define DEBUG_CLIENT
Definition: defines.h:59
cvar_t * Cvar_Get(const char *var_name, const char *var_value, int flags, const char *desc)
Init or return a cvar.
Definition: cvar.cpp:342
#define OBJZERO(obj)
Definition: shared.h:178
#define MAX_VAR
Definition: shared.h:36
List of all mappings for a locale.
static wrapCache_t * hash[MAX_WRAP_HASH]
Definition: r_font.cpp:86
const char * Com_EParse(const char **text, const char *errhead, const char *errinfo, char *target, size_t size)
Parsing function that prints an error message when there is no text in the buffer.
Definition: scripts.cpp:277
#define Mem_CreatePool(name)
Definition: mem.h:32
char const * Q_strstart(char const *str, char const *start)
Matches the start of a string.
Definition: shared.cpp:587
#define Mem_DeletePool(pool)
Definition: mem.h:33
const char * FS_GetCwd(void)
Return current working dir.
Definition: files.cpp:1568
void Com_DPrintf(int level, const char *fmt,...)
A Com_Printf that only shows up if the "developer" cvar is set.
Definition: common.cpp:398
static msgid_t * msgIDHash[MAX_MSGIDHASH]
Definition: cl_language.cpp:52
const char * nativeString
void R_FontShutdown(void)
frees the SDL_ttf fonts
Definition: r_font.cpp:144
#define HASH_Add(hash, elem, index)
Definition: common.h:430
Atomic structure used to define most of the UI.
Definition: ui_nodes.h:80
#define Mem_FreePool(pool)
Definition: mem.h:37
bool disabled
Definition: ui_nodes.h:102
#define MAX_MSGIDS
Definition: cl_language.cpp:38
const char * Sys_GetLocale(void)
Definition: win_shared.cpp:156
cvar_t * s_language
Definition: common.cpp:54
#define Mem_PoolAllocTypeN(type, n, pool)
Definition: mem.h:42
const char * Com_Parse(const char *data_p[], char *target, size_t size, bool replaceWhitespaces)
Parse a token out of a string.
Definition: parse.cpp:107
struct localeMapping_s * next
static int languageCount
void CL_ParseLanguages(const char *name, const char **text)
Parse all language definitions from the script files.
#define MSGIDSIZE
Definition: cl_language.cpp:54
bool Com_ParseList(const char **text, linkedList_t **list)
Definition: scripts.cpp:1385
QGL_EXTERN GLint i
Definition: r_gl.h:113
const char * CL_Translate(const char *t)
char * string
Definition: cvar.h:73
struct language_s language_t
Struct that reflects parsed language definitions from our script files.
localeMapping_t * localeMapping
char * FS_NextScriptHeader(const char *files, const char **name, const char **text)
Definition: files.cpp:1194
QGL_EXTERN GLuint GLsizei GLsizei GLint GLenum GLchar * name
Definition: r_gl.h:110
void UI_RegisterOption(int dataId, uiNode_t *option)
Definition: ui_data.cpp:311
unsigned int Com_HashKey(const char *name, int hashsize)
returns hash key for a string
Definition: shared.cpp:336
void Q_strcat(char *dest, size_t destsize, const char *format,...)
Safely (without overflowing the destination buffer) concatenates two strings.
Definition: shared.cpp:475
bool CL_LanguageTryToSet(const char *localeID)
Cycle through all parsed locale mappings and try to set one after another.
linkedList_t * next
Definition: list.h:32
static int numMsgIDs
Definition: cl_language.cpp:50
uiNode_t * UI_AddOption(uiNode_t **tree, const char *name, const char *label, const char *value)
Append an option to an option list.
Definition: ui_data.cpp:172
const char * localeString
const char * localeID
const char * id
Definition: cl_language.cpp:44
cvar_t * Cvar_Set(const char *varName, const char *value,...)
Sets a cvar value.
Definition: cvar.cpp:615
Primary header for client.
#define Q_streq(a, b)
Definition: shared.h:136
#define Mem_PoolStrDup(in, pool, tagNum)
Definition: mem.h:50
bool modified
Definition: cvar.h:79
#define Mem_PoolAllocType(type, pool)
Definition: mem.h:43
static void CL_NewLanguage(void)
Adjust game for new language: reregister fonts, etc.
void R_FontInit(void)
Definition: r_font.cpp:722
void Com_SkipBlock(const char **text)
Skips a block of {} in our script files.
Definition: parse.cpp:253
void UI_InitFonts(void)
after a video restart we have to reinitialize the fonts
Definition: ui_font.cpp:177