UFO: Alien Invasion
Loading...
Searching...
No Matches
cl_language.cpp
Go to the documentation of this file.
1
5
6/*
7All original material Copyright (C) 2002-2025 UFO: Alien Invasion.
8
9This program is free software; you can redistribute it and/or
10modify it under the terms of the GNU General Public License
11as published by the Free Software Foundation; either version 2
12of the License, or (at your option) any later version.
13
14This program is distributed in the hope that it will be useful,
15but WITHOUT ANY WARRANTY; without even the implied warranty of
16MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
17
18See the GNU General Public License for more details.
19
20You should have received a copy of the GNU General Public License
21along with this program; if not, write to the Free Software
22Foundation, 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
43typedef struct msgid_s {
44 const char* id;
45 const char* text;
46 struct msgid_s* hash_next;
47} msgid_t;
48
50static int numMsgIDs;
51#define MAX_MSGIDHASH 256
53
54#define MSGIDSIZE 65536
55static char* msgIDText;
56
57static 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_DPrintf(DEBUG_CLIENT, "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
113static 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
123const 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;
143
144 if (cl_msgidPool != nullptr) {
146 } else {
147 cl_msgidPool = Mem_CreatePool("msgids");
148 }
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
167typedef struct localeMapping_s {
169 struct localeMapping_s* next;
171
176typedef struct language_s {
177 const char* localeID;
178 const char* localeString;
179 const char* nativeString;
181 struct language_s* next;
182} language_t;
183
185static int languageCount;
186
191static 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
211void 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
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;
270}
271
277static 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;
346 cl_msgidPool = nullptr;
347 msgIDText = nullptr;
348 numMsgIDs = 0;
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
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
399static void CL_NewLanguage (void)
400{
402 R_FontInit();
403 UI_InitFonts();
406}
407
413bool 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)) {
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);
455#endif
456
457 return false;
458}
bool CL_LanguageTryToSet(const char *localeID)
Cycle through all parsed locale mappings and try to set one after another.
static const char * CL_GetMessageID(const char *id)
void CL_LanguageShutdown(void)
void CL_LanguageInit(void)
Fills the options language menu node with the parsed language mappings.
#define MAX_MSGIDHASH
const char * CL_Translate(const char *t)
void CL_ParseLanguages(const char *name, const char **text)
Parse all language definitions from the script files.
void CL_LanguageInitMenu(void)
static const char * CL_GetLocaleID(const char *fullLocale)
Searches the locale script id with the given locale string.
static int numMsgIDs
void CL_ParseMessageIDs(void)
#define MAX_MSGIDS
static cvar_t * fs_i18ndir
static void CL_NewLanguage(void)
Adjust game for new language: reregister fonts, etc.
static int languageCount
static void CL_ParseMessageID(const char *name, const char **text)
static msgid_t * msgIDHash[MAX_MSGIDHASH]
static language_t * languageList
static msgid_t msgIDs[MAX_MSGIDS]
static bool CL_LanguageTest(const char *localeID)
Test given language by trying to set locale.
static char * msgIDText
static memPool_t * cl_msgidPool
#define MSGIDSIZE
memPool_t * cl_genericPool
Definition cl_main.cpp:86
void R_FontInit(void)
Definition r_font.cpp:716
void R_FontSetTruncationMarker(const char *marker)
Definition r_font.cpp:112
void R_FontShutdown(void)
frees the SDL_ttf fonts
Definition r_font.cpp:144
#define _(String)
Definition cl_shared.h:44
Primary header for client.
cvar_t * s_language
Definition common.cpp:54
void Com_DPrintf(int level, const char *fmt,...)
A Com_Printf that only shows up if the "developer" cvar is set.
Definition common.cpp:440
void Com_Error(int code, const char *fmt,...)
Definition common.cpp:459
void Com_Printf(const char *const fmt,...)
Definition common.cpp:428
#define HASH_Add(hash, elem, index)
Definition common.h:407
#define ERR_DROP
Definition common.h:211
#define ERR_FATAL
Definition common.h:210
cvar_t * Cvar_Set(const char *varName, const char *value,...)
Sets a cvar value.
Definition cvar.cpp:615
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 DEBUG_CLIENT
Definition defines.h:59
char * FS_NextScriptHeader(const char *files, const char **name, const char **text)
Definition files.cpp:1196
const char * FS_GetCwd(void)
Return current working dir.
Definition files.cpp:1570
int FS_BuildFileList(const char *fileList)
Build a filelist.
Definition files.cpp:962
bool FS_FileExists(const char *filename,...)
Checks whether a file exists (not in virtual filesystem).
Definition files.cpp:1583
#define MAX_OSPATH
Definition filesys.h:44
#define BASEDIRNAME
Definition filesys.h:34
void Sys_Error(const char *error,...)
Definition g_main.cpp:421
void LIST_Delete(linkedList_t **list)
Definition list.cpp:195
#define Mem_FreePool(pool)
Definition mem.h:37
#define Mem_DeletePool(pool)
Definition mem.h:33
#define Mem_PoolAllocTypeN(type, n, pool)
Definition mem.h:42
#define Mem_PoolStrDup(in, pool, tagNum)
Definition mem.h:50
#define Mem_CreatePool(name)
Definition mem.h:32
#define Mem_PoolAllocType(type, pool)
Definition mem.h:43
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
void Com_SkipBlock(const char **text)
Skips a block of {} in our script files.
Definition parse.cpp:253
Shared parsing functions.
static wrapCache_t * hash[MAX_WRAP_HASH]
Definition r_font.cpp:86
QGL_EXTERN GLint i
Definition r_gl.h:113
QGL_EXTERN GLint GLenum type
Definition r_gl.h:94
QGL_EXTERN GLuint GLsizei GLsizei GLint GLenum GLchar * name
Definition r_gl.h:110
QGL_EXTERN GLuint * id
Definition r_gl.h: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
bool Com_ParseList(const char **text, linkedList_t **list)
Definition scripts.cpp:1363
#define Q_strvalid(string)
Definition shared.h:141
#define Q_streq(a, b)
Definition shared.h:136
#define OBJZERO(obj)
Definition shared.h:178
#define MAX_VAR
Definition shared.h:36
unsigned int Com_HashKey(const char *name, int hashsize)
returns hash key for a string
Definition shared.cpp:336
void Q_strncpyz(char *dest, const char *src, size_t destsize)
Safe strncpy that ensures a trailing zero.
Definition shared.cpp:457
void Q_strcat(char *dest, size_t destsize, const char *format,...)
Safely (without overflowing the destination buffer) concatenates two strings.
Definition shared.cpp:475
char const * Q_strstart(char const *str, char const *start)
Matches the start of a string.
Definition shared.cpp:587
bool Com_sprintf(char *dest, size_t size, const char *fmt,...)
copies formatted string with buffer-size checking
Definition shared.cpp:494
This is a cvar definition. Cvars can be user modified and used in our menus e.g.
Definition cvar.h:71
Struct that reflects parsed language definitions from our script files.
localeMapping_t * localeMapping
const char * nativeString
const char * localeString
const char * localeID
struct language_s * next
linkedList_t * next
Definition list.h:32
List of all mappings for a locale.
struct localeMapping_s * next
const char * id
struct msgid_s * hash_next
const char * text
Atomic structure used to define most of the UI.
Definition ui_nodes.h:80
bool disabled
Definition ui_nodes.h:102
System specific stuff.
const char * Sys_GetLocale(void)
int Sys_Setenv(const char *name, const char *value)
set/unset environment variables (empty value removes it)
const char * Sys_SetLocale(const char *localeID)
void UI_RegisterOption(int dataId, uiNode_t *option)
Definition ui_data.cpp:311
void UI_SortOptions(uiNode_t **first)
Sort options by alphabet.
Definition ui_data.cpp:273
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
@ OPTION_LANGUAGES
Definition ui_dataids.h:75
void UI_InitFonts(void)
after a video restart we have to reinitialize the fonts
Definition ui_font.cpp:177