1 /*
2 * Copyright (C) Volition, Inc. 1999. All rights reserved.
3 *
4 * All source code herein is the property of Volition, Inc. You may not sell
5 * or otherwise commercially exploit the source or things you created based on the
6 * source.
7 *
8 */
9
10
11
12
13 #include <cctype>
14
15 #include "cfile/cfile.h"
16 #include "localization/localize.h"
17 #include "osapi/osregistry.h"
18 #include "parse/encrypt.h"
19 #include "parse/parselo.h"
20 #include "playerman/player.h"
21 #include "mod_table/mod_table.h"
22
23
24 // ------------------------------------------------------------------------------------------------------------
25 // LOCALIZE DEFINES/VARS
26 //
27
28 // general language/localization data ---------------------
29
30 // current language
31 int Lcl_current_lang = LCL_RETAIL_HYBRID;
32 SCP_vector<lang_info> Lcl_languages;
33
34 // These are the original languages supported by FS2. The code expects these languages to be supported even if the tables don't
35
36 lang_info Lcl_builtin_languages[NUM_BUILTIN_LANGUAGES] = {
37 { "English", "", {127,0,176,0,0}, 589986744}, // English ("" is correct; the game data files do not use a language extension for English)
38 { "German", "gr", {164,0,176,0,0}, -1132430286 }, // German
39 { "French", "fr", {164,0,176,0,0}, 0 }, // French
40 { "Polish", "pl", {127,0,176,0,0}, -1131728960}, // Polish
41 };
42
43 int Lcl_special_chars;
44
45 // use these to replace *_BUILD, values before
46 // only 1 will be active at a time
47 int Lcl_fr = 0;
48 int Lcl_gr = 0;
49 int Lcl_pl = 0;
50 int Lcl_en = 1;
51
52 bool *Lcl_unexpected_tstring_check = nullptr;
53
54
55 // executable string localization data --------------------
56
57 // XSTR_SIZE is the total count of unique XSTR index numbers. An index is used to
58 // reference an entry in strings.tbl. This is used for translation of strings from
59 // the english version (in the code) to a foreign version (in the table). Thus, if you
60 // add a new string to the code, you must assign it a new index. Use the number below for
61 // that index and increase the number below by one.
62 #define XSTR_SIZE 1647
63
64
65 // struct to allow for strings.tbl-determined x offset
66 // offset is 0 for english, by default
67 typedef struct {
68 const char *str;
69 int offset_x; // string offset in 640
70 int offset_x_hi; // string offset in 1024
71 } lcl_xstr;
72
73 lcl_xstr Xstr_table[XSTR_SIZE];
74 bool Xstr_inited = false;
75
76
77 // table/mission externalization stuff --------------------
78 #define PARSE_TEXT_BUF_SIZE PARSE_BUF_SIZE
79 #define PARSE_ID_BUF_SIZE 8 // 7 digits and a \0
80
81 SCP_unordered_map<int, char*> Lcl_ext_str;
82
83 // Lcl_ext_str will only keep translations for the active language, so if we're not running in English,
84 // keep the English strings so that we can compare untranslated to English-translated. But to save space,
85 // we only need to keep the NAME_LENGTH strings, since we only need to test mission names.
86 SCP_unordered_map<int, char*> Lcl_ext_str_explicit_default;
87
88
89 // ------------------------------------------------------------------------------------------------------------
90 // LOCALIZE FORWARD DECLARATIONS
91 //
92
93 // parses the string.tbl and reports back only on the languages it found
94 void parse_stringstbl_quick(const char *filename);
95
96
97 // ------------------------------------------------------------------------------------------------------------
98 // LOCALIZE FUNCTIONS
99 //
100
101 // get an index we can use to look into the array
lcl_get_current_lang_index()102 int lcl_get_current_lang_index()
103 {
104 Assertion(Lcl_current_lang >= 0, "Lcl_current_lang should never be negative!");
105
106 if (Lcl_current_lang < NUM_BUILTIN_LANGUAGES)
107 return Lcl_current_lang;
108
109 return LCL_DEFAULT;
110 }
111
112 // initialize localization, if no language is passed - use the language specified in the registry
lcl_init(int lang_init)113 void lcl_init(int lang_init)
114 {
115 char lang_string[128];
116 const char *ret;
117 int lang, idx, i;
118
119 // initialize encryption
120 encrypt_init();
121
122 // set up the first language (which should be English)
123 Lcl_languages.push_back(Lcl_builtin_languages[0]);
124
125 // check string.tbl to see which languages we support
126 try
127 {
128 parse_stringstbl_quick("strings.tbl");
129 }
130 catch (const parse::ParseException& e)
131 {
132 mprintf(("TABLES: Unable to parse '%s'! Error message = %s.\n", "strings.tbl", e.what()));
133 }
134
135 parse_modular_table(NOX("*-lcl.tbm"), parse_stringstbl_quick);
136
137 // if we only have one language at this point, we need to setup the builtin languages as we might be dealing with an old style strings.tbl
138 // which doesn't support anything beyond the builtin languages. Note, we start at i = 1 because we added the first language above.
139 if ((int)Lcl_languages.size() == 1) {
140 for (i=1; i<NUM_BUILTIN_LANGUAGES; i++) {
141 Lcl_languages.push_back(Lcl_builtin_languages[i]);
142 }
143 }
144
145 // read the language from the registry
146 if (lang_init < 0) {
147 memset(lang_string, 0, 128);
148 // default to DEFAULT_LANGUAGE (which should be English so we don't have to put German text
149 // in tstrings in the #default section)
150 ret = os_config_read_string(nullptr, "Language", Lcl_languages[LCL_DEFAULT].lang_name);
151 strcpy_s(lang_string, ret);
152
153 // look it up
154 lang = -1;
155 for(idx = 0; idx < (int)Lcl_languages.size(); idx++){
156 if(!stricmp(Lcl_languages[idx].lang_name, lang_string)){
157 lang = idx;
158 break;
159 }
160 }
161 if(lang < 0){
162 lang = LCL_DEFAULT;
163 }
164 } else {
165 Assert(lang_init == LCL_UNTRANSLATED || lang_init == LCL_RETAIL_HYBRID || (lang_init >= 0 && lang_init < (int)Lcl_languages.size()));
166 lang = lang_init;
167 }
168
169 // and after all that... the default language reverts to hybrid unless we are specifically translating it
170 if (lang == LCL_DEFAULT && !Use_tabled_strings_for_default_language) {
171 lang = LCL_RETAIL_HYBRID;
172 }
173
174 // set the language (this function takes care of setting up file pointers)
175 lcl_set_language(lang);
176 }
177
lcl_close()178 void lcl_close() {
179 lcl_xstr_close();
180 }
181
182 // parses the string.tbl to see which languages are supported. Doesn't read in any strings.
parse_stringstbl_quick(const char * filename)183 void parse_stringstbl_quick(const char *filename)
184 {
185 lang_info language;
186 int lang_idx;
187 int i;
188
189 try {
190 read_file_text(filename, CF_TYPE_TABLES);
191 reset_parse();
192
193 if (optional_string("#Supported Languages")) {
194 while (required_string_either("#End","$Language:")) {
195 required_string("$Language:");
196 stuff_string(language.lang_name, F_RAW, LCL_LANG_NAME_LEN + 1);
197 required_string("+Extension:");
198 stuff_string(language.lang_ext, F_RAW, LCL_LANG_NAME_LEN + 1);
199
200 if (!Unicode_text_mode) {
201 required_string("+Special Character Index:");
202 ubyte special_char;
203 stuff_ubyte(&special_char);
204
205 if (language.special_char_indexes.empty()){
206 language.special_char_indexes.push_back(special_char);
207 }
208 else {
209 language.special_char_indexes[0] = special_char;
210 }
211
212 for (i = 1; i < LCL_MIN_FONTS; ++i) {
213 // default to "none"/0 except for font03 which defaults to 176
214 // NOTE: fonts.tbl may override these values
215 if (i == font::FONT3) {
216 special_char = 176;
217 } else {
218 special_char = 0;
219 }
220
221 // if more than one language is defined, the vector may not be increased to this size already.
222 if (i < (int)language.special_char_indexes.size()) {
223 language.special_char_indexes[i] = special_char;
224 } else {
225 language.special_char_indexes.push_back(special_char);
226 }
227
228 }
229 } else {
230 if (optional_string("+Special Character Index:")) {
231 ubyte temp_index;
232 stuff_ubyte(&temp_index);
233 }
234
235 // Set all indices to valid values
236 for (i = 0; i < LCL_MIN_FONTS; ++i) {
237 if (i >= (int)language.special_char_indexes.size()) {
238 language.special_char_indexes.push_back(0);
239 } else {
240 language.special_char_indexes[i] = 0;
241 }
242 }
243 }
244
245 lang_idx = -1;
246
247 // see if we already have this language
248 for (i = 0; i < (int)Lcl_languages.size(); i++) {
249 if (!strcmp(Lcl_languages[i].lang_name, language.lang_name)) {
250 strcpy_s(Lcl_languages[i].lang_ext, language.lang_ext);
251 Lcl_languages[i].special_char_indexes[0] = language.special_char_indexes[0];
252 lang_idx = i;
253 break;
254 }
255 }
256
257 // if we have a new language, add it.
258 if (lang_idx == -1) {
259 Lcl_languages.push_back(language);
260 }
261 }
262 }
263 }
264 catch (const parse::ParseException& e)
265 {
266 mprintf(("WMCGUI: Unable to parse '%s'! Error message = %s.\n", filename, e.what()));
267 return;
268 }
269 }
270
271 // Unified function for loading strings.tbl and tstrings.tbl (and their modular versions).
272 // The "external" parameter controls which format to load: true for tstrings.tbl, false for strings.tbl
parse_stringstbl_common(const char * filename,const bool external)273 void parse_stringstbl_common(const char *filename, const bool external)
274 {
275 char chr, buf[4096];
276 char language_tag[512];
277 int z, index;
278 char *p_offset = NULL;
279 int offset_lo = 0, offset_hi = 0;
280 int lcl_index = lcl_get_current_lang_index();
281
282 try {
283 read_file_text(filename, CF_TYPE_TABLES);
284 reset_parse();
285
286 // move down to the proper section
287 memset(language_tag, 0, sizeof(language_tag));
288 strcpy_s(language_tag, "#");
289 if (external && (lcl_index == LCL_DEFAULT)) {
290 strcat_s(language_tag, "default");
291 } else {
292 strcat_s(language_tag, Lcl_languages[lcl_index].lang_name);
293 }
294
295 if ( skip_to_string(language_tag) != 1 ) {
296 mprintf(("Language tag %s not found in %s\n", language_tag, filename));
297 return;
298 }
299
300 // parse all the strings in this section of the table
301 while ( !check_for_string("#") ) {
302 int num_offsets_on_this_line = 0;
303
304 stuff_int(&index);
305 if (external) {
306 ignore_white_space();
307 get_string(buf, sizeof(buf));
308 } else {
309 stuff_string(buf, F_RAW, sizeof(buf));
310 }
311
312 if (external && index < 0) {
313 error_display(0, "Invalid tstrings table index specified (%i). The index must be positive.", index);
314 return;
315 } else if (!external && (index < 0 || index >= XSTR_SIZE)) {
316 Error(LOCATION, "Invalid strings table index specified (%i)", index);
317 }
318
319 if (!external) {
320
321 size_t i = strlen(buf);
322
323 while (i--) {
324 if ( !isspace(buf[i]) )
325 break;
326 }
327
328 // trim unnecessary end of string
329 // Assert(buf[i] == '"');
330 if (buf[i] != '"') {
331 // probably an offset on this entry
332
333 // drop down a null terminator (prolly unnecessary)
334 buf[i+1] = 0;
335
336 // back up over the potential offset
337 while ( !is_white_space(buf[i]) )
338 i--;
339
340 // now back up over intervening spaces
341 while ( is_white_space(buf[i]) )
342 i--;
343
344 num_offsets_on_this_line = 1;
345
346 if (buf[i] != '"') {
347 // could have a 2nd offset value (one for 640, one for 1024)
348 // so back up again
349 while ( !is_white_space(buf[i]) )
350 i--;
351
352 // now back up over intervening spaces
353 while ( is_white_space(buf[i]) )
354 i--;
355
356 num_offsets_on_this_line = 2;
357 }
358
359 p_offset = &buf[i+1]; // get ptr to string section with offset in it
360
361 if (buf[i] != '"')
362 error_display(1, "%s is corrupt", filename); // now its an error
363 }
364
365 buf[i] = 0;
366
367 // copy string into buf
368 z = 0;
369 for (i = 1; buf[i]; i++) {
370 chr = buf[i];
371
372 if (chr == '\\') {
373 chr = buf[++i];
374
375 if (chr == 'n')
376 chr = '\n';
377 else if (chr == 'r')
378 chr = '\r';
379 }
380
381 buf[z++] = chr;
382 }
383
384 // null terminator on buf
385 buf[z] = 0;
386 }
387
388 // write into Xstr_table (for strings.tbl) or Lcl_ext_str (for tstrings.tbl)
389 if (Parsing_modular_table) {
390 if ( external && (Lcl_ext_str.find(index) != Lcl_ext_str.end()) ) {
391 vm_free((void *) Lcl_ext_str[index]);
392 Lcl_ext_str.erase(Lcl_ext_str.find(index));
393 } else if ( !external && (Xstr_table[index].str != NULL) ) {
394 vm_free((void *) Xstr_table[index].str);
395 Xstr_table[index].str = NULL;
396 }
397 }
398
399 if (external && (Lcl_ext_str.find(index) != Lcl_ext_str.end())) {
400 Warning(LOCATION, "Tstrings table index %d used more than once", index);
401 } else if (!external && (Xstr_table[index].str != NULL)) {
402 Warning(LOCATION, "Strings table index %d used more than once", index);
403 }
404
405 if (external) {
406 Lcl_ext_str.insert(std::make_pair(index, vm_strdup(buf)));
407 } else {
408 Xstr_table[index].str = vm_strdup(buf);
409 }
410
411 // the rest of this loop applies only to strings.tbl,
412 // so we can move on to the next line if we're reading from tstrings.tbl
413 if (external) {
414 continue;
415 }
416
417 // read offset information, assume 0 if nonexistant
418 if (p_offset != NULL) {
419 if (sscanf(p_offset, "%d%d", &offset_lo, &offset_hi) < num_offsets_on_this_line) {
420 // whatever is in the file ain't a proper offset
421 Error(LOCATION, "%s is corrupt", filename);
422 }
423 }
424
425 Xstr_table[index].offset_x = offset_lo;
426
427 if (num_offsets_on_this_line == 2)
428 Xstr_table[index].offset_x_hi = offset_hi;
429 else
430 Xstr_table[index].offset_x_hi = offset_lo;
431
432 // clear out our vars
433 p_offset = NULL;
434 offset_lo = 0;
435 offset_hi = 0;
436 }
437 }
438 catch (const parse::ParseException& e)
439 {
440 mprintf(("TABLES: Unable to parse 'controlconfigdefaults.tbl'! Error message = %s.\n", e.what()));
441 return;
442 }
443 }
444
parse_stringstbl(const char * filename)445 void parse_stringstbl(const char *filename)
446 {
447 parse_stringstbl_common(filename, false);
448 }
449
parse_tstringstbl(const char * filename)450 void parse_tstringstbl(const char *filename)
451 {
452 parse_stringstbl_common(filename, true);
453 }
454
455 // initialize the xstr table
lcl_xstr_init()456 void lcl_xstr_init()
457 {
458 for (auto &xstr_entry : Xstr_table)
459 xstr_entry.str = nullptr;
460
461 Assertion(Lcl_ext_str.empty() && Lcl_ext_str_explicit_default.empty(), "Localize system was not shut down properly!");
462 Lcl_ext_str.clear();
463 Lcl_ext_str_explicit_default.clear();
464
465
466 try
467 {
468 parse_stringstbl("strings.tbl");
469 }
470 catch (const parse::ParseException& e)
471 {
472 mprintf(("TABLES: Unable to parse '%s'! Error message = %s.\n", "strings.tbl", e.what()));
473 }
474 parse_modular_table(NOX("*-lcl.tbm"), parse_stringstbl);
475
476
477 // If this is a non-English language, parse English and keep a copy of the table that's just the NAME_LENGTH strings
478 if (lcl_get_current_lang_index() != LCL_DEFAULT)
479 {
480 auto saved_language = Lcl_current_lang;
481 Lcl_current_lang = LCL_DEFAULT;
482
483 // same parsing as below
484 try
485 {
486 parse_tstringstbl("tstrings.tbl");
487 }
488 catch (const parse::ParseException& e)
489 {
490 mprintf(("TABLES: Unable to parse '%s'! Error message = %s.\n", "tstrings.tbl", e.what()));
491 }
492 parse_modular_table(NOX("*-tlc.tbm"), parse_tstringstbl);
493
494 // copy entries containing short strings and free the rest
495 for (const auto& entry : Lcl_ext_str)
496 {
497 if (entry.second != nullptr)
498 {
499 if (strlen(entry.second) < NAME_LENGTH)
500 Lcl_ext_str_explicit_default.insert(entry);
501 else
502 vm_free(entry.second);
503 }
504 }
505
506 // reset things so that we can parse the language properly
507 Lcl_ext_str.clear();
508 Lcl_current_lang = saved_language;
509 }
510
511
512 try
513 {
514 parse_tstringstbl("tstrings.tbl");
515 }
516 catch (const parse::ParseException& e)
517 {
518 mprintf(("TABLES: Unable to parse '%s'! Error message = %s.\n", "tstrings.tbl", e.what()));
519 }
520 parse_modular_table(NOX("*-tlc.tbm"), parse_tstringstbl);
521
522
523 Xstr_inited = true;
524 }
525
526
527 // free Xstr table
lcl_xstr_close()528 void lcl_xstr_close()
529 {
530 for (auto &xstr_entry : Xstr_table) {
531 if (xstr_entry.str != nullptr) {
532 vm_free((void *)xstr_entry.str);
533 xstr_entry.str = nullptr;
534 }
535 }
536
537 for (const auto& entry : Lcl_ext_str) {
538 if (entry.second != nullptr) {
539 vm_free(entry.second);
540 }
541 }
542 Lcl_ext_str.clear();
543
544 for (const auto& entry : Lcl_ext_str_explicit_default) {
545 if (entry.second != nullptr) {
546 vm_free(entry.second);
547 }
548 }
549 Lcl_ext_str_explicit_default.clear();
550 }
551
552
553 // set our current language
lcl_set_language(int lang)554 void lcl_set_language(int lang)
555 {
556 Lcl_current_lang = lang;
557
558 if (lang == LCL_UNTRANSLATED) {
559 nprintf(("General", "Setting language to UNTRANSLATED\n"));
560 // but for the purposes of array access, we use the default
561 lang = LCL_DEFAULT;
562 } else if (lang == LCL_RETAIL_HYBRID) {
563 nprintf(("General", "Setting language to RETAIL HYBRID\n"));
564 // but for the purposes of array access, we use the default
565 lang = LCL_DEFAULT;
566 } else {
567 nprintf(("General", "Setting language to %s\n", Lcl_languages[lang].lang_name));
568 }
569
570 Assertion((lang >= 0) && (lang < (int)Lcl_languages.size()), "Attempt to set language to an invalid language");
571
572 // flag the proper language as being active
573 Lcl_special_chars = Lcl_languages[lang].special_char_indexes[0];
574 Lcl_fr = 0;
575 Lcl_gr = 0;
576 Lcl_pl = 0;
577 Lcl_en = 0;
578 if (!strcmp(Lcl_languages[lang].lang_name, Lcl_builtin_languages[LCL_ENGLISH].lang_name)) {
579 Lcl_en = 1;
580 } else if (!strcmp(Lcl_languages[lang].lang_name, Lcl_builtin_languages[LCL_FRENCH].lang_name)) {
581 Lcl_fr = 1;
582 } else if (!strcmp(Lcl_languages[lang].lang_name, Lcl_builtin_languages[LCL_GERMAN].lang_name)) {
583 Lcl_gr = 1;
584 } else if (!strcmp(Lcl_languages[lang].lang_name, Lcl_builtin_languages[LCL_POLISH].lang_name)) {
585 Lcl_pl = 1;
586 }
587 }
588
lcl_get_font_index(int font_num)589 ubyte lcl_get_font_index(int font_num)
590 {
591 int lang = lcl_get_current_lang_index();
592
593 if (Unicode_text_mode) {
594 // In Unicode mode there are no special characters. Some of the code still uses this function in that mode so
595 // we just return 0 to signify that there are no special characters in this font
596 return 0;
597 } else {
598 Assertion((lang >= 0) && (lang < (int)Lcl_languages.size()), "Current language %d is not valid, can't get font indexes. This is a coder error, please report.", lang);
599 Assertion((font_num >= 0) && (font_num < (int)Lcl_languages[lang].special_char_indexes.size()), "Passed an invalid font index, %d. This is a coder error, please report.", font_num);
600
601 return Lcl_languages[lang].special_char_indexes[font_num];
602 }
603 }
604
605 // maybe add localized directory to full path with file name when opening a localized file
lcl_add_dir_to_path_with_filename(char * current_path,size_t path_max)606 int lcl_add_dir_to_path_with_filename(char *current_path, size_t path_max)
607 {
608 int lang = lcl_get_current_lang_index();
609
610 // if the disk extension is 0 length, don't add anything
611 if (strlen(Lcl_languages[lang].lang_ext) <= 0) {
612 return 1;
613 }
614
615 size_t str_size = path_max + 1;
616
617 char *temp = new char[str_size];
618 memset(temp, 0, str_size * sizeof(char));
619
620 // find position of last slash and copy rest of filename (not counting slash) to temp
621 // mark end of current path with '\0', so strcat will work
622 char *last_slash = strrchr(current_path, DIR_SEPARATOR_CHAR);
623 if (last_slash == NULL) {
624 strncpy(temp, current_path, path_max);
625 current_path[0] = '\0';
626 } else {
627 strncpy(temp, last_slash+1, path_max);
628 last_slash[1] = '\0';
629 }
630
631 // add extension
632 strcat_s(current_path, path_max, Lcl_languages[lang].lang_ext);
633 strcat_s(current_path, path_max, DIR_SEPARATOR_STR );
634
635 // copy rest of filename from temp
636 strcat_s(current_path, path_max, temp);
637
638 delete [] temp;
639 return 1;
640 }
641
642
643 // externalization of table/mission files -----------------------
644
lcl_replace_stuff(char * text,size_t max_len,bool force)645 void lcl_replace_stuff(char *text, size_t max_len, bool force)
646 {
647 if (Fred_running && !force)
648 return;
649
650 Assert(text); // Goober5000
651
652 // delegate to SCP_string for the replacements
653 SCP_string temp_text = text;
654 lcl_replace_stuff(temp_text);
655
656 // fill up the original string
657 size_t len = temp_text.copy(text, max_len);
658 text[len] = 0;
659 }
660
661 // Goober5000 - replace stuff in the string, e.g. $callsign with player's callsign
662 // now will also replace $rank with rank, e.g. "Lieutenant"
663 // now will also replace $quote with double quotation marks
664 // now will also replace $semicolon with semicolon mark
665 // now will also replace $slash and $backslash
lcl_replace_stuff(SCP_string & text,bool force)666 void lcl_replace_stuff(SCP_string &text, bool force)
667 {
668 if (Fred_running && !force)
669 return;
670
671 if (!Fred_running && Player != nullptr)
672 {
673 replace_all(text, "$callsign", Player->callsign);
674 replace_all(text, "$rank", Ranks[Player->stats.rank].name);
675 }
676
677 replace_all(text, "$quote", "\"");
678 replace_all(text, "$semicolon", ";");
679 replace_all(text, "$slash", "/");
680 replace_all(text, "$backslash", "\\");
681 }
682
lcl_fred_replace_stuff(char * text,size_t max_len)683 void lcl_fred_replace_stuff(char *text, size_t max_len)
684 {
685 if (!Fred_running)
686 return;
687
688 Assert(text); // Goober5000
689
690 // delegate to SCP_string for the replacements
691 SCP_string temp_text = text;
692 lcl_fred_replace_stuff(temp_text);
693
694 // fill up the original string
695 size_t len = temp_text.copy(text, max_len);
696 text[len] = 0;
697 }
698
lcl_fred_replace_stuff(SCP_string & text)699 void lcl_fred_replace_stuff(SCP_string &text)
700 {
701 if (!Fred_running)
702 return;
703
704 replace_all(text, "\"", "$quote");
705 replace_all(text, ";", "$semicolon");
706 replace_all(text, "/", "$slash");
707 replace_all(text, "\\", "$backslash");
708 }
709
710 // get the localized version of the string. if none exists, return the original string
711 // valid input to this function includes :
712 // "this is some text"
713 // XSTR("wheeee", -1)
714 // XSTR("whee", 2000)
715 // and these should cover all the externalized string cases
716 // NOTE: max_len is the maximum string length, not buffer length
717 // fills in id if non-NULL. a value of -2 indicates it is not an external string
718 // returns true if we were able to extract the XSTR elements (text_str and maybe id are populated)
lcl_ext_localize_sub(const char * in,char * text_str,char * out,size_t max_len,int * id,bool use_default_translation=false)719 bool lcl_ext_localize_sub(const char *in, char *text_str, char *out, size_t max_len, int *id, bool use_default_translation = false)
720 {
721 Assert(in);
722 Assert(out);
723
724 // NOTE: "Token too long" warnings are disabled when Lcl_unexpected_tstring_check is active,
725 // because in such cases we actually anticipate that the length might be exceeded.
726
727 // set up return values
728 auto xstr_str = in;
729 int xstr_id = -2; // default (non-external string) value
730 bool xstr_valid = false;
731
732 auto ch = in;
733 bool attempted_xstr = false;
734
735 // this is a pseudo-goto block, not a loop
736 do {
737 // check to see if this is an XSTR() tag
738 if (strnicmp(ch, "XSTR", 4) != 0)
739 break;
740 ch += 4;
741
742 attempted_xstr = true;
743
744 // the next non-whitespace char should be a (
745 ignore_white_space(&ch);
746 if (*ch != '(')
747 break;
748 ch++;
749
750 // the next should be a quote
751 ignore_white_space(&ch);
752 if (*ch != '\"')
753 break;
754 ch++;
755
756 // now we have the start of the string
757 auto str_start = ch;
758
759 // find the end of the string
760 ch = strchr(ch, '"');
761 if (ch == nullptr)
762 break;
763
764 // now we have the end of the string (past the last character in it)
765 auto str_end = ch;
766 ch++; // skip the quote
767
768 // the next non-whitespace char should be a ,
769 ignore_white_space(&ch);
770 if (*ch != ',')
771 break;
772 ch++;
773
774 // check for number, being mindful of negative
775 ignore_white_space(&ch);
776 bool is_negative = false;
777 if (*ch == '-')
778 {
779 is_negative = true;
780 ch++;
781 }
782 if (!isdigit(*ch))
783 break;
784
785 // now we have the start of the id
786 auto id_start = ch;
787
788 // find all the digits
789 while (isdigit(*ch))
790 ch++;
791
792 // now we have the end of the id (past the last character in it)
793 auto id_end = ch;
794
795 // the next non-whitespace char should be a )
796 ignore_white_space(&ch);
797 if (*ch != ')')
798 break;
799
800 // if we got this far, we know we have a parseable XSTR of some sort
801 xstr_id = -1;
802
803 //
804 // split off the strings and id sections
805 //
806
807 // check bounds
808 auto str_length = str_end - str_start;
809 if (str_length > PARSE_BUF_SIZE - 1)
810 {
811 error_display(0, "String cannot fit within XSTR buffer!\n\n%s\n", str_start);
812 break;
813 }
814
815 // now that we know the boundaries of the actual string in the XSTR() tag, copy it
816 strncpy(text_str, str_start, str_length);
817 text_str[str_length] = '\0';
818
819 // bounds for id too
820 auto id_length = id_end - id_start;
821 if (id_length > PARSE_ID_BUF_SIZE - 1)
822 {
823 error_display(0, "Number cannot fit within XSTR buffer!\n\n%s\n", id_start);
824 break;
825 }
826
827 // copy id
828 char xstr_id_buf[PARSE_ID_BUF_SIZE];
829 strncpy(xstr_id_buf, id_start, id_length);
830 xstr_id_buf[id_length] = '\0';
831
832 //
833 // now we have the information we want
834 //
835
836 xstr_str = text_str;
837 xstr_id = atoi(xstr_id_buf);
838 if (is_negative)
839 xstr_id *= -1;
840 xstr_valid = true;
841
842 // if the localization file is not open, or there's no entry, or we're not translating, return the original string
843 if (!Xstr_inited || (xstr_id < 0) || (!use_default_translation && ((Lcl_current_lang == LCL_UNTRANSLATED) || (Lcl_current_lang == LCL_RETAIL_HYBRID))))
844 break;
845
846 //
847 // we are translating
848 //
849
850 auto lookup_map = &Lcl_ext_str;
851 if (use_default_translation && lcl_get_current_lang_index() != LCL_DEFAULT)
852 {
853 // if we're not already using the default, then switch to our explicit default
854 lookup_map = &Lcl_ext_str_explicit_default;
855 }
856
857 // get the string if it exists
858 if (lookup_map->find(xstr_id) != lookup_map->end())
859 {
860 xstr_str = (*lookup_map)[xstr_id];
861 }
862 // otherwise use what we have, but complain about it
863 else
864 {
865 mprintf(("Could not find entry %d in the external string table!\n", xstr_id));
866 }
867 } while (false);
868
869
870 // set whatever id we have
871 if (id != nullptr)
872 *id = xstr_id;
873
874 // if we made an attempt but failed, let the modder know
875 if (xstr_id == -2 && attempted_xstr)
876 error_display(0, "Malformed XSTR detected:\n\n%s\n", in);
877
878 // copy the entire string (or as much as we can)
879 auto str_len = strlen(xstr_str);
880 strncpy(out, xstr_str, max_len);
881 if (str_len > max_len)
882 out[max_len] = '\0';
883 else
884 out[str_len] = '\0';
885
886 // maybe warn about length
887 if (str_len > max_len && !Lcl_unexpected_tstring_check)
888 error_display(0, "Token too long: [%s]. Length = " SIZE_T_ARG ". Max is " SIZE_T_ARG ".\n", xstr_str, str_len, max_len);
889
890 return xstr_valid;
891 }
892
893 // ditto for SCP_string
lcl_ext_localize_sub(const SCP_string & in,SCP_string & text_str,SCP_string & out,int * id,bool use_default_translation=false)894 bool lcl_ext_localize_sub(const SCP_string &in, SCP_string &text_str, SCP_string &out, int *id, bool use_default_translation = false)
895 {
896 // set up return values
897 auto xstr_str = in.c_str();
898 int xstr_id = -2; // default (non-external string) value
899 bool xstr_valid = false;
900
901 auto ch = in.c_str();
902 bool attempted_xstr = false;
903
904 // this is a pseudo-goto block, not a loop
905 do {
906 // check to see if this is an XSTR() tag
907 if (strnicmp(ch, "XSTR", 4) != 0)
908 break;
909 ch += 4;
910
911 attempted_xstr = true;
912
913 // the next non-whitespace char should be a (
914 ignore_white_space(&ch);
915 if (*ch != '(')
916 break;
917 ch++;
918
919 // the next should be a quote
920 ignore_white_space(&ch);
921 if (*ch != '\"')
922 break;
923 ch++;
924
925 // now we have the start of the string
926 auto str_start = ch;
927
928 // find the end of the string
929 ch = strchr(ch, '"');
930 if (ch == nullptr)
931 break;
932
933 // now we have the end of the string (past the last character in it)
934 auto str_end = ch;
935 ch++; // skip the quote
936
937 // the next non-whitespace char should be a ,
938 ignore_white_space(&ch);
939 if (*ch != ',')
940 break;
941 ch++;
942
943 // check for number, being mindful of negative
944 ignore_white_space(&ch);
945 bool is_negative = false;
946 if (*ch == '-')
947 {
948 is_negative = true;
949 ch++;
950 }
951 if (!isdigit(*ch))
952 break;
953
954 // now we have the start of the id
955 auto id_start = ch;
956
957 // find all the digits
958 while (isdigit(*ch))
959 ch++;
960
961 // now we have the end of the id (past the last character in it)
962 auto id_end = ch;
963
964 // the next non-whitespace char should be a )
965 ignore_white_space(&ch);
966 if (*ch != ')')
967 break;
968
969 // if we got this far, we know we have a parseable XSTR of some sort
970 xstr_id = -1;
971
972 //
973 // split off the strings and id sections
974 //
975
976 // now that we know the boundaries of the actual string in the XSTR() tag, copy it
977 text_str.assign(str_start, str_end);
978
979 // bounds for id too
980 auto id_length = id_end - id_start;
981 if (id_length > PARSE_ID_BUF_SIZE - 1)
982 {
983 error_display(0, "Number cannot fit within XSTR buffer!\n\n%s\n", id_start);
984 break;
985 }
986
987 // copy id
988 char xstr_id_buf[PARSE_ID_BUF_SIZE];
989 strncpy(xstr_id_buf, id_start, id_length);
990 xstr_id_buf[id_length] = '\0';
991
992 //
993 // now we have the information we want
994 //
995
996 xstr_str = text_str.c_str();
997 xstr_id = atoi(xstr_id_buf);
998 if (is_negative)
999 xstr_id *= -1;
1000 xstr_valid = true;
1001
1002 // if the localization file is not open, or there's no entry, or we're not translating, return the original string
1003 if (!Xstr_inited || (xstr_id < 0) || (!use_default_translation && ((Lcl_current_lang == LCL_UNTRANSLATED) || (Lcl_current_lang == LCL_RETAIL_HYBRID))))
1004 break;
1005
1006 //
1007 // we are translating
1008 //
1009
1010 auto lookup_map = &Lcl_ext_str;
1011 if (use_default_translation && lcl_get_current_lang_index() != LCL_DEFAULT)
1012 {
1013 // if we're not already using the default, then switch to our explicit default
1014 lookup_map = &Lcl_ext_str_explicit_default;
1015 }
1016
1017 // get the string if it exists
1018 if (lookup_map->find(xstr_id) != lookup_map->end())
1019 {
1020 xstr_str = (*lookup_map)[xstr_id];
1021 }
1022 // otherwise use what we have, but complain about it
1023 else
1024 {
1025 mprintf(("Could not find entry %d in the external string table!\n", xstr_id));
1026 }
1027 } while (false);
1028
1029
1030 // set whatever id we have
1031 if (id != nullptr)
1032 *id = xstr_id;
1033
1034 // if we made an attempt but failed, let the modder know
1035 if (xstr_id == -2 && attempted_xstr)
1036 error_display(0, "Malformed XSTR detected:\n\n%s\n", in.c_str());
1037
1038 // copy the entire string
1039 out = xstr_str;
1040
1041 return xstr_valid;
1042 }
1043
1044 // Goober5000 - wrapper for lcl_ext_localize_sub; used because lcl_replace_stuff has to
1045 // be called *after* the translation is done, and the original function returned in so
1046 // many places that it would be messy to call lcl_replace_stuff everywhere
1047 // Addendum: Now, of course, it provides a handy way to encapsulate the unexpected tstring check.
lcl_ext_localize(const char * in,char * out,size_t max_len,int * id)1048 void lcl_ext_localize(const char *in, char *out, size_t max_len, int *id)
1049 {
1050 // buffer for the untranslated string inside the XSTR tag
1051 char text_str[PARSE_BUF_SIZE] = "";
1052
1053 // if we're doing this extra check, then we have to compare the untranslated string with the default language string and see if they're different
1054 if (Lcl_unexpected_tstring_check)
1055 {
1056 // explicitly use the default table for the translation lookup
1057 bool extracted = lcl_ext_localize_sub(in, text_str, out, max_len, id, true);
1058
1059 // only check short strings, since those are the only ones we keep in the explicit default table
1060 if (strlen(text_str) < NAME_LENGTH)
1061 {
1062 // the untranslated and default-translated strings should always be identical, so if they're different, it might mean we have some data from a different mod
1063 if (extracted && strcmp(text_str, out) != 0)
1064 *Lcl_unexpected_tstring_check = true;
1065 }
1066
1067 // at this point, we can go back to our usual language and do the translation for real
1068 if (lcl_get_current_lang_index() != LCL_DEFAULT)
1069 lcl_ext_localize_sub(in, text_str, out, max_len, id);
1070 }
1071 // most of the time we're not going to do the check, so localize as normal
1072 else
1073 {
1074 // do XSTR translation
1075 lcl_ext_localize_sub(in, text_str, out, max_len, id);
1076 }
1077
1078 // do translation of $callsign, $rank, etc.
1079 lcl_replace_stuff(out, max_len);
1080 }
1081
1082 // ditto for SCP_string
lcl_ext_localize(const SCP_string & in,SCP_string & out,int * id)1083 void lcl_ext_localize(const SCP_string &in, SCP_string &out, int *id)
1084 {
1085 // buffer for the untranslated string inside the XSTR tag
1086 SCP_string text_str = "";
1087
1088 // if we're doing this extra check, then we have to compare the untranslated string with the default language string and see if they're different
1089 if (Lcl_unexpected_tstring_check)
1090 {
1091 // explicitly use the default table for the translation lookup
1092 bool extracted = lcl_ext_localize_sub(in, text_str, out, id, true);
1093
1094 // only check short strings, since those are the only ones we keep in the explicit default table
1095 if (text_str.length() < NAME_LENGTH)
1096 {
1097 // the untranslated and default-translated strings should always be identical, so if they're different, it might mean we have some data from a different mod
1098 if (extracted && text_str != out)
1099 *Lcl_unexpected_tstring_check = true;
1100 }
1101
1102 // at this point, we can go back to our usual language and do the translation for real
1103 if (lcl_get_current_lang_index() != LCL_DEFAULT)
1104 lcl_ext_localize_sub(in, text_str, out, id);
1105 }
1106 // most of the time we're not going to do the check, so localize as normal
1107 else
1108 {
1109 // do XSTR translation
1110 lcl_ext_localize_sub(in, text_str, out, id);
1111 }
1112
1113 // do translation of $callsign, $rank, etc.
1114 lcl_replace_stuff(out);
1115 }
1116
1117 // translate the specified string based upon the current language
XSTR(const char * str,int index,bool force_lookup)1118 const char *XSTR(const char *str, int index, bool force_lookup)
1119 {
1120 if(!Xstr_inited)
1121 {
1122 Int3();
1123 return str;
1124 }
1125
1126 // for some internal strings, such as the ones we loaded using $Has XStr:,
1127 // we want to force a lookup even if we're normally untranslated
1128 if (Lcl_current_lang != LCL_UNTRANSLATED || force_lookup)
1129 {
1130 // perform a lookup
1131 if (index >= 0 && index < XSTR_SIZE)
1132 {
1133 // return translation of string
1134 if (Xstr_table[index].str)
1135 return Xstr_table[index].str;
1136 #ifndef NDEBUG
1137 else
1138 {
1139 // make sure missing strings are only logged once
1140 static SCP_unordered_set<int> Warned_xstr_indexes;
1141 if (Warned_xstr_indexes.count(index) == 0)
1142 {
1143 Warned_xstr_indexes.insert(index);
1144 mprintf(("No XSTR entry in strings.tbl for index %d\n", index));
1145 }
1146 }
1147 #endif
1148 }
1149 }
1150
1151 // can't translate; return original English string
1152 return str;
1153 }
1154
1155 // retrieve the offset for a localized string
lcl_get_xstr_offset(int index,int res)1156 int lcl_get_xstr_offset(int index, int res)
1157 {
1158 if (res == GR_640) {
1159 return Xstr_table[index].offset_x;
1160 } else {
1161 return Xstr_table[index].offset_x_hi;
1162 }
1163 }
1164
1165
1166 // ------------------------------------------------------------------------------------------------------------
1167 // LOCALIZE FORWARD DEFINITIONS
1168 //
1169
lcl_get_language_name(char * lang_name)1170 void lcl_get_language_name(char *lang_name)
1171 {
1172 int lang = lcl_get_current_lang_index();
1173
1174 Assert(lang >= 0 && lang < (int)Lcl_languages.size());
1175 strcpy(lang_name, Lcl_languages[lang].lang_name);
1176 }
1177
1178 // ------------------------------------------------------------------
1179 // lcl_translate_wep_name_gr()
1180 //
1181 // For displaying weapon names in german version
1182 // since we can't actually just change them outright.
1183 //
lcl_translate_wep_name_gr(char * name)1184 void lcl_translate_wep_name_gr(char *name)
1185 {
1186 if (!strcmp(name, "Morning Star")) {
1187 strcpy(name, "Morgenstern");
1188 } else if (!strcmp(name, "MorningStar")) {
1189 strcpy(name, "Morgenstern D");
1190 } else if (!strcmp(name, "UD-8 Kayser")) {
1191 strcpy(name, "Kayserstrahl");
1192 } else if (!strcmp(name, "UD-D Kayser")) {
1193 strcpy(name, "Kayserstrahl");
1194 }
1195 }
1196
1197 // ------------------------------------------------------------------
1198 // lcl_translate_brief_icon_name_gr()
1199 //
1200 // For displaying ship names in german version
1201 // since we can't actually just change them outright.
1202 //
lcl_translate_brief_icon_name_gr(char * name)1203 void lcl_translate_brief_icon_name_gr(char *name)
1204 {
1205 char *pos;
1206 char buf[128];
1207
1208 if (!stricmp(name, "Subspace Portal")) {
1209 strcpy(name, "Subraum Portal");
1210
1211 } else if (!stricmp(name, "Alpha Wing")) {
1212 strcpy(name, "Alpha");
1213
1214 } else if (!stricmp(name, "Beta Wing")) {
1215 strcpy(name, "Beta");
1216
1217 } else if (!stricmp(name, "Zeta Wing")) {
1218 strcpy(name, "Zeta");
1219
1220 } else if (!stricmp(name, "Capella Node")) {
1221 strcpy(name, "Capella");
1222
1223 } else if (!stricmp(name, "Hostile")) {
1224 strcpy(name, "Gegner");
1225
1226 } else if (!stricmp(name, "Hostile Craft")) {
1227 strcpy(name, "Gegner");
1228
1229 } else if (!stricmp(name, "Rebel Wing")) {
1230 strcpy(name, "Rebellen");
1231
1232 } else if (!stricmp(name, "Rebel Fleet")) {
1233 strcpy(name, "Rebellenflotte");
1234
1235 } else if (!stricmp(name, "Sentry Gun")) {
1236 strcpy(name, "Gesch\x81tz");
1237
1238 } else if (!stricmp(name, "Cargo")) {
1239 strcpy(name, "Fracht");
1240
1241 } else if (!stricmp(name, "Knossos Device")) {
1242 strcpy(name, "Knossosger\x84t");
1243
1244 } else if (!stricmp(name, "Support")) {
1245 strcpy(name, "Versorger");
1246
1247 } else if (!stricmp(name, "Unknown")) {
1248 strcpy(name, "Unbekannt");
1249
1250 } else if (!stricmp(name, "Instructor")) {
1251 strcpy(name, "Ausbilder");
1252
1253 } else if (!stricmp(name, "Jump Node")) {
1254 strcpy(name, "Sprungknoten");
1255
1256 } else if (!stricmp(name, "Escort")) {
1257 strcpy(name, "Geleitschutz");
1258
1259 } else if (!stricmp(name, "Asteroid Field")) {
1260 strcpy(name, "Asteroidenfeld");
1261
1262 } else if (!stricmp(name, "Enif Station")) {
1263 strcpy(name, "Station Enif");
1264
1265 } else if (!stricmp(name, "Rally Point")) {
1266 strcpy(name, "Sammelpunkt");
1267
1268 } else if ((pos = strstr(name, "Transport")) != NULL) {
1269 pos += 9; // strlen of "transport"
1270 strcpy_s(buf, "Transporter");
1271 strcat_s(buf, pos);
1272 strcpy(name, buf);
1273
1274 } else if ((pos = strstr(name, "Jump Node")) != NULL) {
1275 pos += 9; // strlen of "jump node"
1276 strcpy_s(buf, "Sprungknoten");
1277 strcat_s(buf, pos);
1278 strcpy(name, buf);
1279
1280 } else if (!stricmp(name, "Orion under repair")) {
1281 strcpy(name, "Orion wird repariert");
1282
1283 // SOTY-specific ones below!
1284
1285 } else if (!stricmp(name, "Wayfarer Station")) {
1286 strcpy(name, "Station Wayfarer");
1287 } else if (!stricmp(name, "Enemy")) {
1288 strcpy(name, "Gegner");
1289 } else if (!stricmp(name, "Supply Depot")) {
1290 strcpy(name, "Nachschubdepot");
1291 } else if (!stricmp(name, "Fighter Escort")) {
1292 strcpy(name, "Jagdschutz");
1293 } else if (!stricmp(name, "Shivans")) {
1294 strcpy(name, "Shivaner");
1295 } else if (!stricmp(name, "NTF Base of Operations")) {
1296 strcpy(name, "NTF-Operationsbasis");
1297 } else if (!stricmp(name, "NTF Bombers")) {
1298 strcpy(name, "NTF-Bomber");
1299 } else if (!stricmp(name, "NTF Fighters")) {
1300 strcpy(name, "NTF-J\x84ger");
1301 } else if (!stricmp(name, "Sentry")) {
1302 strcpy(name, "Sperrgesch\x81tz");
1303 } else if (!stricmp(name, "Cargo Containers")) {
1304 strcpy(name, "Frachtbeh\x84lter");
1305 } else if (!stricmp(name, "NTF Reinforcements")) {
1306 strcpy(name, "NTF-Verst\x84rkungen");
1307 } else if (!stricmp(name, "NTF Base")) {
1308 strcpy(name, "NTF-St\x81tzpunkt");
1309 } else if (!stricmp(name, "Refugee Convoy")) {
1310 strcpy(name, "Fl\x81""chtlingskonvoi");
1311 } else if (!stricmp(name, "Food Convoy")) {
1312 strcpy(name, "Nachschubkonvoi");
1313 } else if (!stricmp(name, "Governor's Shuttle")) {
1314 strcpy(name, "F\x84hre des Gouverneurs");
1315 } else if (!stricmp(name, "GTVA Patrol")) {
1316 strcpy(name, "GTVA-Patrouille");
1317 } else if (!stricmp(name, "Escort fighters")) {
1318 strcpy(name, "Geleitschutz");
1319 } else if (!stricmp(name, "Nagada Outpost")) {
1320 strcpy(name, "Nagada-Aussenposten");
1321 } else if (!stricmp(name, "Fighters")) {
1322 strcpy(name, "J\x84ger");
1323 } else if (!stricmp(name, "Bombers")) {
1324 strcpy(name, "Bomber");
1325 } else if (!stricmp(name, "Enemy Destroyers")) {
1326 strcpy(name, "Feindliche Zerst\x94rer");
1327 } else if (!stricmp(name, "Ross 128 System")) {
1328 strcpy(name, "System Ross 128");
1329 } else if (!stricmp(name, "Knossos Station")) {
1330 strcpy(name, "Knossos-Station");
1331 } else if (!stricmp(name, "Transporters")) {
1332 strcpy(name, "Transporter");
1333 } else if (!stricmp(name, "Pirates?")) {
1334 strcpy(name, "Piraten?");
1335 } else if (!stricmp(name, "Escorts")) {
1336 strcpy(name, "Geleitschutz");
1337 } else if (!stricmp(name, "Shivan Fighters")) {
1338 strcpy(name, "J\x84ger");
1339 } else if (!stricmp(name, "Shivan Territory")) {
1340 strcpy(name, "Shivaner");
1341 }
1342 }
1343
1344 // ------------------------------------------------------------------
1345 // lcl_translate_brief_icon_name_pl()
1346 //
1347 // For displaying ship names in polish version
1348 // since we can't actually just change them outright.
1349 //
lcl_translate_brief_icon_name_pl(char * name)1350 void lcl_translate_brief_icon_name_pl(char *name)
1351 {
1352 char *pos;
1353 char buf[128];
1354
1355 if (!stricmp(name, "Subspace Portal")) {
1356 strcpy(name, "Portal podprz.");
1357
1358 } else if (!stricmp(name, "Alpha Wing")) {
1359 strcpy(name, "Alfa");
1360
1361 } else if (!stricmp(name, "Beta Wing")) {
1362 strcpy(name, "Beta");
1363
1364 } else if (!stricmp(name, "Zeta Wing")) {
1365 strcpy(name, "Zeta");
1366
1367 } else if (!stricmp(name, "Capella Node")) {
1368 strcpy(name, "Capella");
1369
1370 } else if (!stricmp(name, "Hostile")) {
1371 strcpy(name, "Wr\xF3g");
1372
1373 } else if (!stricmp(name, "Hostile Craft")) {
1374 strcpy(name, "Wr\xF3g");
1375
1376 } else if (!stricmp(name, "Rebel Wing")) {
1377 strcpy(name, "Rebelianci");
1378
1379 } else if (!stricmp(name, "Rebel Fleet")) {
1380 strcpy(name, "Flota Rebelii");
1381
1382 } else if (!stricmp(name, "Sentry Gun")) {
1383 strcpy(name, "Dzia\xB3o str.");
1384
1385 } else if (!stricmp(name, "Cargo")) {
1386 strcpy(name, "\xA3\x61\x64unek");
1387
1388 } else if (!stricmp(name, "Knossos Device")) {
1389 strcpy(name, "Urz. Knossos");
1390
1391 } else if (!stricmp(name, "Support")) {
1392 strcpy(name, "Wsparcie");
1393
1394 } else if (!stricmp(name, "Unknown")) {
1395 strcpy(name, "Nieznany");
1396
1397 } else if (!stricmp(name, "Instructor")) {
1398 strcpy(name, "Instruktor");
1399
1400 } else if (!stricmp(name, "Jump Node")) {
1401 strcpy(name, "W\xEAze\xB3 skokowy");
1402
1403 } else if (!stricmp(name, "Escort")) {
1404 strcpy(name, "Eskorta");
1405
1406 } else if (!stricmp(name, "Asteroid Field")) {
1407 strcpy(name, "Pole asteroid");
1408
1409 } else if (!stricmp(name, "Enif Station")) {
1410 strcpy(name, "Stacja Enif");
1411
1412 } else if (!stricmp(name, "Rally Point")) {
1413 strcpy(name, "Pkt zborny");
1414
1415 } else if ((pos = strstr(name, "Transport")) != NULL) {
1416 pos += 9; // strlen of "transport"
1417 strcpy_s(buf, "Transportowiec");
1418 strcat_s(buf, pos);
1419 strcpy(name, buf);
1420
1421 } else if ((pos = strstr(name, "Jump Node")) != NULL) {
1422 pos += 9; // strlen of "jump node"
1423 strcpy_s(buf, "W\xEAze\xB3 skokowy");
1424 strcat_s(buf, pos);
1425 strcpy(name, buf);
1426
1427 } else if (!stricmp(name, "Orion under repair")) {
1428 strcpy(name, "Naprawiany Orion");
1429 }
1430 }
1431
1432 // ------------------------------------------------------------------
1433 // lcl_translate_ship_name_gr()
1434 //
1435 // For displaying ship names in german version in the briefing
1436 // since we can't actually just change them outright.
1437 //
lcl_translate_ship_name_gr(char * name)1438 void lcl_translate_ship_name_gr(char *name)
1439 {
1440 if (!strcmp(name, "GTDR Amazon Advanced")) {
1441 strcpy(name, "GTDR Amazon VII");
1442 }
1443 }
1444
1445 // ------------------------------------------------------------------
1446 // lcl_translate_targetbox_name_gr()
1447 //
1448 // For displaying ship names in german version in the targetbox
1449 // since we can't actually just change them outright.
1450 //
lcl_translate_targetbox_name_gr(char * name)1451 void lcl_translate_targetbox_name_gr(char *name)
1452 {
1453 char *pos;
1454 char buf[128];
1455
1456 if ((pos = strstr(name, "Sentry")) != NULL) {
1457 pos += 6; // strlen of "sentry"
1458 strcpy_s(buf, "Sperrgesch\x81tz");
1459 strcat_s(buf, pos);
1460 strcpy(name, buf);
1461
1462 } else if ((pos = strstr(name, "Support")) != NULL) {
1463 pos += 7; // strlen of "support"
1464 strcpy_s(buf, "Versorger");
1465 strcat_s(buf, pos);
1466 strcpy(name, buf);
1467
1468 } else if ((pos = strstr(name, "Unknown")) != NULL) {
1469 pos += 7; // strlen of "unknown"
1470 strcpy_s(buf, "Unbekannt");
1471 strcat_s(buf, pos);
1472 strcpy(name, buf);
1473
1474 } else if ((pos = strstr(name, "Drone")) != NULL) {
1475 pos += 5; // strlen of "drone"
1476 strcpy_s(buf, "Drohne");
1477 strcat_s(buf, pos);
1478 strcpy(name, buf);
1479
1480 } else if ((pos = strstr(name, "Jump Node")) != NULL) {
1481 pos += 9; // strlen of "jump node"
1482 strcpy_s(buf, "Sprungknoten");
1483 strcat_s(buf, pos);
1484 strcpy(name, buf);
1485
1486 } else if (!stricmp(name, "Instructor")) {
1487 strcpy(name, "Ausbilder");
1488
1489 } else if (!stricmp(name, "NTF Vessel")) {
1490 strcpy(name, "NTF-Schiff");
1491
1492 } else if (!stricmp(name, "Enif Station")) {
1493 strcpy(name, "Station Enif");
1494 }
1495 }
1496
1497 // ------------------------------------------------------------------
1498 // lcl_translate_targetbox_name_pl()
1499 //
1500 // For displaying ship names in polish version in the targetbox
1501 // since we can't actually just change them outright.
1502 //
lcl_translate_targetbox_name_pl(char * name)1503 void lcl_translate_targetbox_name_pl(char *name)
1504 {
1505 char *pos;
1506 char buf[128];
1507
1508 if ((pos = strstr(name, "Sentry")) != NULL) {
1509 pos += 6; // strlen of "sentry"
1510 strcpy_s(buf, "Stra\xBFnik");
1511 strcat_s(buf, pos);
1512 strcpy(name, buf);
1513
1514 } else if ((pos = strstr(name, "Support")) != NULL) {
1515 pos += 7; // strlen of "support"
1516 strcpy_s(buf, "Wsparcie");
1517 strcat_s(buf, pos);
1518 strcpy(name, buf);
1519
1520 } else if ((pos = strstr(name, "Unknown")) != NULL) {
1521 pos += 7; // strlen of "unknown"
1522 strcpy_s(buf, "Nieznany");
1523 strcat_s(buf, pos);
1524 strcpy(name, buf);
1525
1526 } else if ((pos = strstr(name, "Drone")) != NULL) {
1527 pos += 5; // strlen of "drone"
1528 strcpy_s(buf, "Sonda");
1529 strcat_s(buf, pos);
1530 strcpy(name, buf);
1531
1532 } else if ((pos = strstr(name, "Jump Node")) != NULL) {
1533 pos += 9; // strlen of "jump node"
1534 strcpy_s(buf, "W\xEAze\xB3 skokowy");
1535 strcat_s(buf, pos);
1536 strcpy(name, buf);
1537
1538 } else if (!stricmp(name, "Instructor")) {
1539 strcpy(name, "Instruktor");
1540
1541 } else if (!stricmp(name, "NTF Vessel")) {
1542 strcpy(name, "Okr\xEAt NTF");
1543
1544 } else if (!stricmp(name, "Enif Station")) {
1545 strcpy(name, "Stacja Enif");
1546 }
1547 }
1548
1549 // this is just a hack to display translated names without actually changing the names,
1550 // which would break stuff
1551 // (this used to be in medals.cpp)
lcl_translate_medal_name_gr(char * name)1552 void lcl_translate_medal_name_gr(char *name)
1553 {
1554 if (!strcmp(name, "Epsilon Pegasi Liberation")) {
1555 strcpy(name, "Epsilon Pegasi Befreiungsmedaille");
1556
1557 } else if (!strcmp(name, "Imperial Order of Vasuda")) {
1558 strcpy(name, "Imperialer Orden von Vasuda ");
1559
1560 } else if (!strcmp(name, "Distinguished Flying Cross")) {
1561 strcpy(name, "Fliegerkreuz Erster Klasse");
1562
1563 } else if (!strcmp(name, "SOC Service Medallion")) {
1564 strcpy(name, "SEK-Dienstmedaille ");
1565
1566 } else if (!strcmp(name, "Intelligence Cross")) {
1567 strcpy(name, "Geheimdienstkreuz am Bande");
1568
1569 } else if (!strcmp(name, "Order of Galatea")) {
1570 strcpy(name, "Orden von Galatea ");
1571
1572 } else if (!strcmp(name, "Meritorious Unit Commendation")) {
1573 strcpy(name, "Ehrenspange der Allianz");
1574
1575 } else if (!strcmp(name, "Medal of Valor")) {
1576 strcpy(name, "Tapferkeitsmedaille ");
1577
1578 } else if (!strcmp(name, "GTVA Legion of Honor")) {
1579 strcpy(name, "Orden der GTVA-Ehrenlegion");
1580
1581 } else if (!strcmp(name, "Allied Defense Citation")) {
1582 strcpy(name, "Alliierte Abwehrspange ");
1583
1584 } else if (!strcmp(name, "Nebula Campaign Victory Star")) {
1585 strcpy(name, "Nebel-Siegesstern");
1586
1587 } else if (!strcmp(name, "NTF Campaign Victory Star")) {
1588 strcpy(name, "NTF-Siegesstern ");
1589
1590 } else if (!strcmp(name, "Rank")) {
1591 strcpy(name, "Dienstgrad");
1592
1593 } else if (!strcmp(name, "Wings")) {
1594 strcpy(name, "Fliegerspange");
1595
1596 } else if (!strcmp(name, "Ace")) {
1597 strcpy(name, "Flieger-As");
1598
1599 } else if (!strcmp(name, "Double Ace")) {
1600 strcpy(name, "Doppel-As ");
1601
1602 } else if (!strcmp(name, "Triple Ace")) {
1603 strcpy(name, "Dreifach-As ");
1604
1605 } else if (!strcmp(name, "SOC Unit Crest")) {
1606 strcpy(name, "SEK-Abzeichen ");
1607 }
1608 }
1609
1610 // this is just a hack to display translated names without actually changing the names,
1611 // which would break stuff
1612 // (this used to be in medals.cpp)
lcl_translate_medal_name_pl(char * name)1613 void lcl_translate_medal_name_pl(char *name)
1614 {
1615 if (!strcmp(name, "Epsilon Pegasi Liberation")) {
1616 strcpy(name, "Order Wyzwolenia Epsilon Pegasi");
1617
1618 } else if (!strcmp(name, "Imperial Order of Vasuda")) {
1619 strcpy(name, "Imperialny Order Vasudy");
1620
1621 } else if (!strcmp(name, "Distinguished Flying Cross")) {
1622 strcpy(name, "Krzy\xBF Wybitnego Pilota");
1623
1624 } else if (!strcmp(name, "SOC Service Medallion")) {
1625 strcpy(name, "Krzy\xBF S\xB3u\xBF\x62 Specjalnych");
1626
1627 } else if (!strcmp(name, "Intelligence Cross")) {
1628 strcpy(name, "Krzy\xBF Wywiadu");
1629
1630 } else if (!strcmp(name, "Order of Galatea")) {
1631 strcpy(name, "Order Galatei");
1632
1633 } else if (!strcmp(name, "Meritorious Unit Commendation")) {
1634 strcpy(name, "Medal Pochwalny");
1635
1636 } else if (!strcmp(name, "Medal of Valor")) {
1637 strcpy(name, "Medal za Odwag\xEA");
1638
1639 } else if (!strcmp(name, "GTVA Legion of Honor")) {
1640 strcpy(name, "Legia Honorowa GTVA");
1641
1642 } else if (!strcmp(name, "Allied Defense Citation")) {
1643 strcpy(name, "Order za Obron\xEA Sojuszu");
1644
1645 } else if (!strcmp(name, "Nebula Campaign Victory Star")) {
1646 strcpy(name, "Gwiazda Wiktorii Kampanii w Mg\xB3\x61wicy");
1647
1648 } else if (!strcmp(name, "NTF Campaign Victory Star")) {
1649 strcpy(name, "Gwiazda Wiktorii Kampanii NTF");
1650
1651 } else if (!strcmp(name, "Rank")) {
1652 strcpy(name, "Ranga");
1653
1654 } else if (!strcmp(name, "Wings")) {
1655 strcpy(name, "Skrzyd\xB3\x61");
1656
1657 } else if (!strcmp(name, "Ace")) {
1658 strcpy(name, "As");
1659
1660 } else if (!strcmp(name, "Double Ace")) {
1661 strcpy(name, "Podw\xF3jny As");
1662
1663 } else if (!strcmp(name, "Triple Ace")) {
1664 strcpy(name, "Potr\xF3jny As");
1665
1666 } else if (!strcmp(name, "SOC Unit Crest")) {
1667 strcpy(name, "Tarcza S\xB3u\xBF\x62 Specjalnych");
1668 }
1669 }
1670