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