Update: Translations from eints
[openttd-github.git] / src / strings.cpp
blob7fa1bad9c1772c715e9ac9880e070f8405978a7a
1 /*
2 * This file is part of OpenTTD.
3 * OpenTTD is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, version 2.
4 * OpenTTD is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
5 * See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with OpenTTD. If not, see <http://www.gnu.org/licenses/>.
6 */
8 /** @file strings.cpp Handling of translated strings. */
10 #include "stdafx.h"
11 #include "currency.h"
12 #include "station_base.h"
13 #include "town.h"
14 #include "waypoint_base.h"
15 #include "depot_base.h"
16 #include "industry.h"
17 #include "newgrf_text.h"
18 #include "fileio_func.h"
19 #include "signs_base.h"
20 #include "fontdetection.h"
21 #include "error.h"
22 #include "error_func.h"
23 #include "strings_func.h"
24 #include "rev.h"
25 #include "core/endian_func.hpp"
26 #include "timer/timer_game_calendar.h"
27 #include "vehicle_base.h"
28 #include "engine_base.h"
29 #include "language.h"
30 #include "townname_func.h"
31 #include "string_func.h"
32 #include "company_base.h"
33 #include "smallmap_gui.h"
34 #include "window_func.h"
35 #include "debug.h"
36 #include "game/game_text.hpp"
37 #include "network/network_content_gui.h"
38 #include "newgrf_engine.h"
39 #include "core/backup_type.hpp"
40 #include "gfx_layout.h"
41 #include <stack>
42 #include <charconv>
44 #include "table/strings.h"
45 #include "table/control_codes.h"
46 #include "3rdparty/fmt/std.h"
48 #include "strings_internal.h"
50 #include "safeguards.h"
52 std::string _config_language_file; ///< The file (name) stored in the configuration.
53 LanguageList _languages; ///< The actual list of language meta data.
54 const LanguageMetadata *_current_language = nullptr; ///< The currently loaded language.
56 TextDirection _current_text_dir; ///< Text direction of the currently selected language.
58 #ifdef WITH_ICU_I18N
59 std::unique_ptr<icu::Collator> _current_collator; ///< Collator for the language currently in use.
60 #endif /* WITH_ICU_I18N */
62 ArrayStringParameters<20> _global_string_params;
64 /**
65 * Prepare the string parameters for the next formatting run. This means
66 * resetting the type information and resetting the offset to the begin.
68 void StringParameters::PrepareForNextRun()
70 for (auto &param : this->parameters) param.type = 0;
71 this->offset = 0;
75 /**
76 * Get the next parameter from our parameters.
77 * This updates the offset, so the next time this is called the next parameter
78 * will be read.
79 * @return The next parameter.
81 const StringParameter &StringParameters::GetNextParameterReference()
83 assert(this->next_type == 0 || (SCC_CONTROL_START <= this->next_type && this->next_type <= SCC_CONTROL_END));
84 if (this->offset >= this->parameters.size()) {
85 throw std::out_of_range("Trying to read invalid string parameter");
88 auto &param = this->parameters[this->offset++];
89 if (param.type != 0 && param.type != this->next_type) {
90 this->next_type = 0;
91 throw std::out_of_range("Trying to read string parameter with wrong type");
93 param.type = this->next_type;
94 this->next_type = 0;
95 return param;
99 /**
100 * Set a string parameter \a v at index \a n in the global string parameter array.
101 * @param n Index of the string parameter.
102 * @param v Value of the string parameter.
104 void SetDParam(size_t n, uint64_t v)
106 _global_string_params.SetParam(n, v);
110 * Get the current string parameter at index \a n from the global string parameter array.
111 * @param n Index of the string parameter.
112 * @return Value of the requested string parameter.
114 uint64_t GetDParam(size_t n)
116 return std::get<uint64_t>(_global_string_params.GetParam(n));
120 * Set DParam n to some number that is suitable for string size computations.
121 * @param n Index of the string parameter.
122 * @param max_value The biggest value which shall be displayed.
123 * For the result only the number of digits of \a max_value matter.
124 * @param min_count Minimum number of digits independent of \a max.
125 * @param size Font of the number
127 void SetDParamMaxValue(size_t n, uint64_t max_value, uint min_count, FontSize size)
129 uint num_digits = 1;
130 while (max_value >= 10) {
131 num_digits++;
132 max_value /= 10;
134 SetDParamMaxDigits(n, std::max(min_count, num_digits), size);
138 * Set DParam n to some number that is suitable for string size computations.
139 * @param n Index of the string parameter.
140 * @param count Number of digits which shall be displayable.
141 * @param size Font of the number
143 void SetDParamMaxDigits(size_t n, uint count, FontSize size)
145 uint front = 0;
146 uint next = 0;
147 GetBroadestDigit(&front, &next, size);
148 uint64_t val = count > 1 ? front : next;
149 for (; count > 1; count--) {
150 val = 10 * val + next;
152 SetDParam(n, val);
156 * Copy the parameters from the backup into the global string parameter array.
157 * @param backup The backup to copy from.
159 void CopyInDParam(const std::span<const StringParameterData> backup)
161 for (size_t i = 0; i < backup.size(); i++) {
162 _global_string_params.SetParam(i, backup[i]);
167 * Copy \a num string parameters from the global string parameter array to the \a backup.
168 * @param backup The backup to write to.
169 * @param num Number of string parameters to copy.
171 void CopyOutDParam(std::vector<StringParameterData> &backup, size_t num)
173 backup.resize(num);
174 for (size_t i = 0; i < backup.size(); i++) {
175 backup[i] = _global_string_params.GetParam(i);
180 * Checks whether the global string parameters have changed compared to the given backup.
181 * @param backup The backup to check against.
182 * @return True when the parameters have changed, otherwise false.
184 bool HaveDParamChanged(const std::span<const StringParameterData> backup)
186 for (size_t i = 0; i < backup.size(); i++) {
187 if (backup[i] != _global_string_params.GetParam(i)) return true;
189 return false;
192 static void StationGetSpecialString(StringBuilder &builder, StationFacility x);
193 static void GetSpecialTownNameString(StringBuilder &builder, int ind, uint32_t seed);
194 static void GetSpecialNameString(StringBuilder &builder, int ind, StringParameters &args);
196 static void FormatString(StringBuilder &builder, const char *str, StringParameters &args, uint case_index = 0, bool game_script = false, bool dry_run = false);
198 struct LanguagePack : public LanguagePackHeader {
199 char data[]; // list of strings
202 struct LanguagePackDeleter {
203 void operator()(LanguagePack *langpack)
205 /* LanguagePack is in fact reinterpreted char[], we need to reinterpret it back to free it properly. */
206 delete[] reinterpret_cast<char*>(langpack);
210 struct LoadedLanguagePack {
211 std::unique_ptr<LanguagePack, LanguagePackDeleter> langpack;
213 std::vector<char *> offsets;
215 std::array<uint, TEXT_TAB_END> langtab_num; ///< Offset into langpack offs
216 std::array<uint, TEXT_TAB_END> langtab_start; ///< Offset into langpack offs
219 static LoadedLanguagePack _langpack;
221 static bool _scan_for_gender_data = false; ///< Are we scanning for the gender of the current string? (instead of formatting it)
224 const char *GetStringPtr(StringID string)
226 switch (GetStringTab(string)) {
227 case TEXT_TAB_GAMESCRIPT_START: return GetGameStringPtr(GetStringIndex(string));
228 /* 0xD0xx and 0xD4xx IDs have been converted earlier. */
229 case TEXT_TAB_OLD_NEWGRF: NOT_REACHED();
230 case TEXT_TAB_NEWGRF_START: return GetGRFStringPtr(GetStringIndex(string));
231 default: return _langpack.offsets[_langpack.langtab_start[GetStringTab(string)] + GetStringIndex(string)];
236 * Get a parsed string with most special stringcodes replaced by the string parameters.
237 * @param builder The builder of the string.
238 * @param string The ID of the string to parse.
239 * @param args Arguments for the string.
240 * @param case_index The "case index". This will only be set when FormatString wants to print the string in a different case.
241 * @param game_script The string is coming directly from a game script.
243 void GetStringWithArgs(StringBuilder &builder, StringID string, StringParameters &args, uint case_index, bool game_script)
245 if (string == 0) {
246 GetStringWithArgs(builder, STR_UNDEFINED, args);
247 return;
250 uint index = GetStringIndex(string);
251 StringTab tab = GetStringTab(string);
253 switch (tab) {
254 case TEXT_TAB_TOWN:
255 if (index >= 0xC0 && !game_script) {
256 try {
257 GetSpecialTownNameString(builder, index - 0xC0, args.GetNextParameter<uint32_t>());
258 } catch (const std::runtime_error &e) {
259 Debug(misc, 0, "GetStringWithArgs: {}", e.what());
260 builder += "(invalid string parameter)";
262 return;
264 break;
266 case TEXT_TAB_SPECIAL:
267 if (index >= 0xE4 && !game_script) {
268 try {
269 GetSpecialNameString(builder, index - 0xE4, args);
270 } catch (const std::runtime_error &e) {
271 Debug(misc, 0, "GetStringWithArgs: {}", e.what());
272 builder += "(invalid string parameter)";
274 return;
276 break;
278 case TEXT_TAB_OLD_CUSTOM:
279 /* Old table for custom names. This is no longer used */
280 if (!game_script) {
281 FatalError("Incorrect conversion of custom name string.");
283 break;
285 case TEXT_TAB_GAMESCRIPT_START: {
286 FormatString(builder, GetGameStringPtr(index), args, case_index, true);
287 return;
290 case TEXT_TAB_OLD_NEWGRF:
291 NOT_REACHED();
293 case TEXT_TAB_NEWGRF_START: {
294 FormatString(builder, GetGRFStringPtr(index), args, case_index);
295 return;
298 default:
299 break;
302 if (index >= _langpack.langtab_num[tab]) {
303 if (game_script) {
304 return GetStringWithArgs(builder, STR_UNDEFINED, args);
306 FatalError("String 0x{:X} is invalid. You are probably using an old version of the .lng file.\n", string);
309 FormatString(builder, GetStringPtr(string), args, case_index);
314 * Resolve the given StringID into a std::string with all the associated
315 * DParam lookups and formatting.
316 * @param string The unique identifier of the translatable string.
317 * @return The std::string of the translated string.
319 std::string GetString(StringID string)
321 _global_string_params.PrepareForNextRun();
322 return GetStringWithArgs(string, _global_string_params);
326 * Get a parsed string with most special stringcodes replaced by the string parameters.
327 * @param string The ID of the string to parse.
328 * @param args Arguments for the string.
329 * @return The parsed string.
331 std::string GetStringWithArgs(StringID string, StringParameters &args)
333 std::string result;
334 StringBuilder builder(result);
335 GetStringWithArgs(builder, string, args);
336 return result;
340 * This function is used to "bind" a C string to a OpenTTD dparam slot.
341 * @param n slot of the string
342 * @param str string to bind
344 void SetDParamStr(size_t n, const char *str)
346 _global_string_params.SetParam(n, str);
350 * This function is used to "bind" the C string of a std::string to a OpenTTD dparam slot.
351 * The caller has to ensure that the std::string reference remains valid while the string is shown.
352 * @param n slot of the string
353 * @param str string to bind
355 void SetDParamStr(size_t n, const std::string &str)
357 _global_string_params.SetParam(n, str);
361 * This function is used to "bind" the std::string to a OpenTTD dparam slot.
362 * Contrary to the other \c SetDParamStr functions, this moves the string into
363 * the parameter slot.
364 * @param n slot of the string
365 * @param str string to bind
367 void SetDParamStr(size_t n, std::string &&str)
369 _global_string_params.SetParam(n, std::move(str));
372 static const char *GetDecimalSeparator()
374 const char *decimal_separator = _settings_game.locale.digit_decimal_separator.c_str();
375 if (StrEmpty(decimal_separator)) decimal_separator = _langpack.langpack->digit_decimal_separator;
376 return decimal_separator;
380 * Format a number into a string.
381 * @param builder the string builder to write to
382 * @param number the number to write down
383 * @param separator the thousands-separator to use
385 static void FormatNumber(StringBuilder &builder, int64_t number, const char *separator)
387 static const int max_digits = 20;
388 uint64_t divisor = 10000000000000000000ULL;
389 int thousands_offset = (max_digits - 1) % 3;
391 if (number < 0) {
392 builder += '-';
393 number = -number;
396 uint64_t num = number;
397 uint64_t tot = 0;
398 for (int i = 0; i < max_digits; i++) {
399 uint64_t quot = 0;
400 if (num >= divisor) {
401 quot = num / divisor;
402 num = num % divisor;
404 if ((tot |= quot) || i == max_digits - 1) {
405 builder += '0' + quot; // quot is a single digit
406 if ((i % 3) == thousands_offset && i < max_digits - 1) builder += separator;
409 divisor /= 10;
413 static void FormatCommaNumber(StringBuilder &builder, int64_t number)
415 const char *separator = _settings_game.locale.digit_group_separator.c_str();
416 if (StrEmpty(separator)) separator = _langpack.langpack->digit_group_separator;
417 FormatNumber(builder, number, separator);
420 static void FormatNoCommaNumber(StringBuilder &builder, int64_t number)
422 fmt::format_to(builder, "{}", number);
425 static void FormatZerofillNumber(StringBuilder &builder, int64_t number, int count)
427 fmt::format_to(builder, "{:0{}d}", number, count);
430 static void FormatHexNumber(StringBuilder &builder, uint64_t number)
432 fmt::format_to(builder, "0x{:X}", number);
436 * Format a given number as a number of bytes with the SI prefix.
437 * @param builder the string builder to write to
438 * @param number the number of bytes to write down
440 static void FormatBytes(StringBuilder &builder, int64_t number)
442 assert(number >= 0);
444 /* 1 2^10 2^20 2^30 2^40 2^50 2^60 */
445 const char * const iec_prefixes[] = {"", "Ki", "Mi", "Gi", "Ti", "Pi", "Ei"};
446 uint id = 1;
447 while (number >= 1024 * 1024) {
448 number /= 1024;
449 id++;
452 if (number < 1024) {
453 id = 0;
454 fmt::format_to(builder, "{}", number);
455 } else if (number < 1024 * 10) {
456 fmt::format_to(builder, "{}{}{:02}", number / 1024, GetDecimalSeparator(), (number % 1024) * 100 / 1024);
457 } else if (number < 1024 * 100) {
458 fmt::format_to(builder, "{}{}{:01}", number / 1024, GetDecimalSeparator(), (number % 1024) * 10 / 1024);
459 } else {
460 assert(number < 1024 * 1024);
461 fmt::format_to(builder, "{}", number / 1024);
464 assert(id < lengthof(iec_prefixes));
465 fmt::format_to(builder, NBSP "{}B", iec_prefixes[id]);
468 static void FormatYmdString(StringBuilder &builder, TimerGameCalendar::Date date, uint case_index)
470 TimerGameCalendar::YearMonthDay ymd = TimerGameCalendar::ConvertDateToYMD(date);
472 auto tmp_params = MakeParameters(ymd.day + STR_DAY_NUMBER_1ST - 1, STR_MONTH_ABBREV_JAN + ymd.month, ymd.year);
473 FormatString(builder, GetStringPtr(STR_FORMAT_DATE_LONG), tmp_params, case_index);
476 static void FormatMonthAndYear(StringBuilder &builder, TimerGameCalendar::Date date, uint case_index)
478 TimerGameCalendar::YearMonthDay ymd = TimerGameCalendar::ConvertDateToYMD(date);
480 auto tmp_params = MakeParameters(STR_MONTH_JAN + ymd.month, ymd.year);
481 FormatString(builder, GetStringPtr(STR_FORMAT_DATE_SHORT), tmp_params, case_index);
484 static void FormatTinyOrISODate(StringBuilder &builder, TimerGameCalendar::Date date, StringID str)
486 TimerGameCalendar::YearMonthDay ymd = TimerGameCalendar::ConvertDateToYMD(date);
488 /* Day and month are zero-padded with ZEROFILL_NUM, hence the two 2s. */
489 auto tmp_params = MakeParameters(ymd.day, 2, ymd.month + 1, 2, ymd.year);
490 FormatString(builder, GetStringPtr(str), tmp_params);
493 static void FormatGenericCurrency(StringBuilder &builder, const CurrencySpec *spec, Money number, bool compact)
495 /* We are going to make number absolute for printing, so
496 * keep this piece of data as we need it later on */
497 bool negative = number < 0;
499 number *= spec->rate;
501 /* convert from negative */
502 if (number < 0) {
503 builder.Utf8Encode(SCC_PUSH_COLOUR);
504 builder.Utf8Encode(SCC_RED);
505 builder += '-';
506 number = -number;
509 /* Add prefix part, following symbol_pos specification.
510 * Here, it can can be either 0 (prefix) or 2 (both prefix and suffix).
511 * The only remaining value is 1 (suffix), so everything that is not 1 */
512 if (spec->symbol_pos != 1) builder += spec->prefix;
514 StringID number_str = STR_NULL;
516 /* For huge numbers, compact the number. */
517 if (compact) {
518 /* Take care of the thousand rounding. Having 1 000 000 k
519 * and 1 000 M is inconsistent, so always use 1 000 M. */
520 if (number >= Money(1'000'000'000'000'000) - 500'000'000) {
521 number = (number + Money(500'000'000'000)) / Money(1'000'000'000'000);
522 number_str = STR_CURRENCY_SHORT_TERA;
523 } else if (number >= Money(1'000'000'000'000) - 500'000) {
524 number = (number + 500'000'000) / 1'000'000'000;
525 number_str = STR_CURRENCY_SHORT_GIGA;
526 } else if (number >= 1'000'000'000 - 500) {
527 number = (number + 500'000) / 1'000'000;
528 number_str = STR_CURRENCY_SHORT_MEGA;
529 } else if (number >= 1'000'000) {
530 number = (number + 500) / 1'000;
531 number_str = STR_CURRENCY_SHORT_KILO;
535 const char *separator = _settings_game.locale.digit_group_separator_currency.c_str();
536 if (StrEmpty(separator)) separator = GetCurrency().separator.c_str();
537 if (StrEmpty(separator)) separator = _langpack.langpack->digit_group_separator_currency;
538 FormatNumber(builder, number, separator);
539 if (number_str != STR_NULL) {
540 auto tmp_params = ArrayStringParameters<0>();
541 FormatString(builder, GetStringPtr(number_str), tmp_params);
544 /* Add suffix part, following symbol_pos specification.
545 * Here, it can can be either 1 (suffix) or 2 (both prefix and suffix).
546 * The only remaining value is 1 (prefix), so everything that is not 0 */
547 if (spec->symbol_pos != 0) builder += spec->suffix;
549 if (negative) {
550 builder.Utf8Encode(SCC_POP_COLOUR);
555 * Determine the "plural" index given a plural form and a number.
556 * @param count The number to get the plural index of.
557 * @param plural_form The plural form we want an index for.
558 * @return The plural index for the given form.
560 static int DeterminePluralForm(int64_t count, int plural_form)
562 /* The absolute value determines plurality */
563 uint64_t n = abs(count);
565 switch (plural_form) {
566 default:
567 NOT_REACHED();
569 /* Two forms: singular used for one only.
570 * Used in:
571 * Danish, Dutch, English, German, Norwegian, Swedish, Estonian, Finnish,
572 * Greek, Hebrew, Italian, Portuguese, Spanish, Esperanto */
573 case 0:
574 return n != 1 ? 1 : 0;
576 /* Only one form.
577 * Used in:
578 * Hungarian, Japanese, Turkish */
579 case 1:
580 return 0;
582 /* Two forms: singular used for 0 and 1.
583 * Used in:
584 * French, Brazilian Portuguese */
585 case 2:
586 return n > 1 ? 1 : 0;
588 /* Three forms: special cases for 0, and numbers ending in 1 except when ending in 11.
589 * Note: Cases are out of order for hysterical reasons. '0' is last.
590 * Used in:
591 * Latvian */
592 case 3:
593 return n % 10 == 1 && n % 100 != 11 ? 0 : n != 0 ? 1 : 2;
595 /* Five forms: special cases for 1, 2, 3 to 6, and 7 to 10.
596 * Used in:
597 * Gaelige (Irish) */
598 case 4:
599 return n == 1 ? 0 : n == 2 ? 1 : n < 7 ? 2 : n < 11 ? 3 : 4;
601 /* Three forms: special cases for numbers ending in 1 except when ending in 11, and 2 to 9 except when ending in 12 to 19.
602 * Used in:
603 * Lithuanian */
604 case 5:
605 return n % 10 == 1 && n % 100 != 11 ? 0 : n % 10 >= 2 && (n % 100 < 10 || n % 100 >= 20) ? 1 : 2;
607 /* Three forms: special cases for numbers ending in 1 except when ending in 11, and 2 to 4 except when ending in 12 to 14.
608 * Used in:
609 * Croatian, Russian, Ukrainian */
610 case 6:
611 return n % 10 == 1 && n % 100 != 11 ? 0 : n % 10 >= 2 && n % 10 <= 4 && (n % 100 < 10 || n % 100 >= 20) ? 1 : 2;
613 /* Three forms: special cases for 1, and numbers ending in 2 to 4 except when ending in 12 to 14.
614 * Used in:
615 * Polish */
616 case 7:
617 return n == 1 ? 0 : n % 10 >= 2 && n % 10 <= 4 && (n % 100 < 10 || n % 100 >= 20) ? 1 : 2;
619 /* Four forms: special cases for numbers ending in 01, 02, and 03 to 04.
620 * Used in:
621 * Slovenian */
622 case 8:
623 return n % 100 == 1 ? 0 : n % 100 == 2 ? 1 : n % 100 == 3 || n % 100 == 4 ? 2 : 3;
625 /* Two forms: singular used for numbers ending in 1 except when ending in 11.
626 * Used in:
627 * Icelandic */
628 case 9:
629 return n % 10 == 1 && n % 100 != 11 ? 0 : 1;
631 /* Three forms: special cases for 1, and 2 to 4
632 * Used in:
633 * Czech, Slovak */
634 case 10:
635 return n == 1 ? 0 : n >= 2 && n <= 4 ? 1 : 2;
637 /* Two forms: cases for numbers ending with a consonant, and with a vowel.
638 * Korean doesn't have the concept of plural, but depending on how a
639 * number is pronounced it needs another version of a particle.
640 * As such the plural system is misused to give this distinction.
642 case 11:
643 switch (n % 10) {
644 case 0: // yeong
645 case 1: // il
646 case 3: // sam
647 case 6: // yuk
648 case 7: // chil
649 case 8: // pal
650 return 0;
652 case 2: // i
653 case 4: // sa
654 case 5: // o
655 case 9: // gu
656 return 1;
658 default:
659 NOT_REACHED();
662 /* Four forms: special cases for 1, 0 and numbers ending in 02 to 10, and numbers ending in 11 to 19.
663 * Used in:
664 * Maltese */
665 case 12:
666 return (n == 1 ? 0 : n == 0 || (n % 100 > 1 && n % 100 < 11) ? 1 : (n % 100 > 10 && n % 100 < 20) ? 2 : 3);
667 /* Four forms: special cases for 1 and 11, 2 and 12, 3 .. 10 and 13 .. 19, other
668 * Used in:
669 * Scottish Gaelic */
670 case 13:
671 return ((n == 1 || n == 11) ? 0 : (n == 2 || n == 12) ? 1 : ((n > 2 && n < 11) || (n > 12 && n < 20)) ? 2 : 3);
673 /* Three forms: special cases for 1, 0 and numbers ending in 01 to 19.
674 * Used in:
675 * Romanian */
676 case 14:
677 return n == 1 ? 0 : (n == 0 || (n % 100 > 0 && n % 100 < 20)) ? 1 : 2;
681 static const char *ParseStringChoice(const char *b, uint form, StringBuilder &builder)
683 /* <NUM> {Length of each string} {each string} */
684 uint n = (uint8_t)*b++;
685 uint pos, i, mypos = 0;
687 for (i = pos = 0; i != n; i++) {
688 uint len = (uint8_t)*b++;
689 if (i == form) mypos = pos;
690 pos += len;
693 builder += b + mypos;
694 return b + pos;
697 /** Helper for unit conversion. */
698 struct UnitConversion {
699 double factor; ///< Amount to multiply or divide upon conversion.
702 * Convert value from OpenTTD's internal unit into the displayed value.
703 * @param input The input to convert.
704 * @param round Whether to round the value or not.
705 * @return The converted value.
707 int64_t ToDisplay(int64_t input, bool round = true) const
709 return round
710 ? (int64_t)std::round(input * this->factor)
711 : (int64_t)(input * this->factor);
715 * Convert the displayed value back into a value of OpenTTD's internal unit.
716 * @param input The input to convert.
717 * @param round Whether to round the value up or not.
718 * @param divider Divide the return value by this.
719 * @return The converted value.
721 int64_t FromDisplay(int64_t input, bool round = true, int64_t divider = 1) const
723 return round
724 ? (int64_t)std::round(input / this->factor / divider)
725 : (int64_t)(input / this->factor / divider);
729 /** Information about a specific unit system. */
730 struct Units {
731 UnitConversion c; ///< Conversion
732 StringID s; ///< String for the unit
733 unsigned int decimal_places; ///< Number of decimal places embedded in the value. For example, 1 if the value is in tenths, and 3 if the value is in thousandths.
736 /** Information about a specific unit system with a long variant. */
737 struct UnitsLong {
738 UnitConversion c; ///< Conversion
739 StringID s; ///< String for the short variant of the unit
740 StringID l; ///< String for the long variant of the unit
741 unsigned int decimal_places; ///< Number of decimal places embedded in the value. For example, 1 if the value is in tenths, and 3 if the value is in thousandths.
744 /** Unit conversions for velocity. */
745 static const Units _units_velocity_calendar[] = {
746 { { 1.0 }, STR_UNITS_VELOCITY_IMPERIAL, 0 },
747 { { 1.609344 }, STR_UNITS_VELOCITY_METRIC, 0 },
748 { { 0.44704 }, STR_UNITS_VELOCITY_SI, 0 },
749 { { 0.578125 }, STR_UNITS_VELOCITY_GAMEUNITS_DAY, 1 },
750 { { 0.868976 }, STR_UNITS_VELOCITY_KNOTS, 0 },
753 /** Unit conversions for velocity. */
754 static const Units _units_velocity_realtime[] = {
755 { { 1.0 }, STR_UNITS_VELOCITY_IMPERIAL, 0 },
756 { { 1.609344 }, STR_UNITS_VELOCITY_METRIC, 0 },
757 { { 0.44704 }, STR_UNITS_VELOCITY_SI, 0 },
758 { { 0.289352 }, STR_UNITS_VELOCITY_GAMEUNITS_SEC, 1 },
759 { { 0.868976 }, STR_UNITS_VELOCITY_KNOTS, 0 },
762 /** Unit conversions for power. */
763 static const Units _units_power[] = {
764 { { 1.0 }, STR_UNITS_POWER_IMPERIAL, 0 },
765 { { 1.01387 }, STR_UNITS_POWER_METRIC, 0 },
766 { { 0.745699 }, STR_UNITS_POWER_SI, 0 },
769 /** Unit conversions for power to weight. */
770 static const Units _units_power_to_weight[] = {
771 { { 0.907185 }, STR_UNITS_POWER_IMPERIAL_TO_WEIGHT_IMPERIAL, 1 },
772 { { 1.0 }, STR_UNITS_POWER_IMPERIAL_TO_WEIGHT_METRIC, 1 },
773 { { 1.0 }, STR_UNITS_POWER_IMPERIAL_TO_WEIGHT_SI, 1 },
774 { { 0.919768 }, STR_UNITS_POWER_METRIC_TO_WEIGHT_IMPERIAL, 1 },
775 { { 1.01387 }, STR_UNITS_POWER_METRIC_TO_WEIGHT_METRIC, 1 },
776 { { 1.01387 }, STR_UNITS_POWER_METRIC_TO_WEIGHT_SI, 1 },
777 { { 0.676487 }, STR_UNITS_POWER_SI_TO_WEIGHT_IMPERIAL, 1 },
778 { { 0.745699 }, STR_UNITS_POWER_SI_TO_WEIGHT_METRIC, 1 },
779 { { 0.745699 }, STR_UNITS_POWER_SI_TO_WEIGHT_SI, 1 },
782 /** Unit conversions for weight. */
783 static const UnitsLong _units_weight[] = {
784 { { 1.102311 }, STR_UNITS_WEIGHT_SHORT_IMPERIAL, STR_UNITS_WEIGHT_LONG_IMPERIAL, 0 },
785 { { 1.0 }, STR_UNITS_WEIGHT_SHORT_METRIC, STR_UNITS_WEIGHT_LONG_METRIC, 0 },
786 { { 1000.0 }, STR_UNITS_WEIGHT_SHORT_SI, STR_UNITS_WEIGHT_LONG_SI, 0 },
789 /** Unit conversions for volume. */
790 static const UnitsLong _units_volume[] = {
791 { { 264.172 }, STR_UNITS_VOLUME_SHORT_IMPERIAL, STR_UNITS_VOLUME_LONG_IMPERIAL, 0 },
792 { { 1000.0 }, STR_UNITS_VOLUME_SHORT_METRIC, STR_UNITS_VOLUME_LONG_METRIC, 0 },
793 { { 1.0 }, STR_UNITS_VOLUME_SHORT_SI, STR_UNITS_VOLUME_LONG_SI, 0 },
796 /** Unit conversions for force. */
797 static const Units _units_force[] = {
798 { { 0.224809 }, STR_UNITS_FORCE_IMPERIAL, 0 },
799 { { 0.101972 }, STR_UNITS_FORCE_METRIC, 0 },
800 { { 0.001 }, STR_UNITS_FORCE_SI, 0 },
803 /** Unit conversions for height. */
804 static const Units _units_height[] = {
805 { { 3.0 }, STR_UNITS_HEIGHT_IMPERIAL, 0 }, // "Wrong" conversion factor for more nicer GUI values
806 { { 1.0 }, STR_UNITS_HEIGHT_METRIC, 0 },
807 { { 1.0 }, STR_UNITS_HEIGHT_SI, 0 },
810 /** Unit conversions for time in calendar days or wallclock seconds */
811 static const Units _units_time_days_or_seconds[] = {
812 { { 1 }, STR_UNITS_DAYS, 0 },
813 { { 2 }, STR_UNITS_SECONDS, 0 },
816 /** Unit conversions for time in calendar months or wallclock minutes */
817 static const Units _units_time_months_or_minutes[] = {
818 { { 1 }, STR_UNITS_MONTHS, 0 },
819 { { 1 }, STR_UNITS_MINUTES, 0 },
822 /** Unit conversions for time in calendar years or economic periods */
823 static const Units _units_time_years_or_periods[] = {
824 { { 1 }, STR_UNITS_YEARS, 0 },
825 { { 1 }, STR_UNITS_PERIODS, 0 },
828 /** Unit conversions for time in calendar years or wallclock minutes */
829 static const Units _units_time_years_or_minutes[] = {
830 { { 1 }, STR_UNITS_YEARS, 0 },
831 { { 12 }, STR_UNITS_MINUTES, 0 },
835 * Get the correct velocity units depending on the vehicle type and whether we're using real-time units.
836 * @param type VehicleType to convert velocity for.
837 * @return The Units for the proper vehicle and time mode.
839 static const Units GetVelocityUnits(VehicleType type)
841 uint8_t setting = (type == VEH_SHIP || type == VEH_AIRCRAFT) ? _settings_game.locale.units_velocity_nautical : _settings_game.locale.units_velocity;
843 assert(setting < lengthof(_units_velocity_calendar));
844 assert(setting < lengthof(_units_velocity_realtime));
846 if (TimerGameEconomy::UsingWallclockUnits()) return _units_velocity_realtime[setting];
848 return _units_velocity_calendar[setting];
852 * Convert the given (internal) speed to the display speed.
853 * @param speed the speed to convert
854 * @return the converted speed.
856 uint ConvertSpeedToDisplaySpeed(uint speed, VehicleType type)
858 /* For historical reasons we don't want to mess with the
859 * conversion for speed. So, don't round it and keep the
860 * original conversion factors instead of the real ones. */
861 return GetVelocityUnits(type).c.ToDisplay(speed, false);
865 * Convert the given display speed to the (internal) speed.
866 * @param speed the speed to convert
867 * @return the converted speed.
869 uint ConvertDisplaySpeedToSpeed(uint speed, VehicleType type)
871 return GetVelocityUnits(type).c.FromDisplay(speed);
875 * Convert the given km/h-ish speed to the display speed.
876 * @param speed the speed to convert
877 * @return the converted speed.
879 uint ConvertKmhishSpeedToDisplaySpeed(uint speed, VehicleType type)
881 return GetVelocityUnits(type).c.ToDisplay(speed * 10, false) / 16;
885 * Convert the given display speed to the km/h-ish speed.
886 * @param speed the speed to convert
887 * @return the converted speed.
889 uint ConvertDisplaySpeedToKmhishSpeed(uint speed, VehicleType type)
891 return GetVelocityUnits(type).c.FromDisplay(speed * 16, true, 10);
895 * Parse most format codes within a string and write the result to a buffer.
896 * @param builder The string builder to write the final string to.
897 * @param str_arg The original string with format codes.
898 * @param args Pointer to extra arguments used by various string codes.
899 * @param dry_run True when the args' type data is not yet initialized.
901 static void FormatString(StringBuilder &builder, const char *str_arg, StringParameters &args, uint case_index, bool game_script, bool dry_run)
903 size_t orig_offset = args.GetOffset();
905 if (!dry_run) {
907 * This function is normally called with `dry_run` false, then we call this function again
908 * with `dry_run` being true. The dry run is required for the gender formatting. For the
909 * gender determination we need to format a sub string to get the gender, but for that we
910 * need to know as what string control code type the specific parameter is encoded. Since
911 * gendered words can be before the "parameter" words, this needs to be determined before
912 * the actual formatting.
914 std::string buffer;
915 StringBuilder dry_run_builder(buffer);
916 if (UsingNewGRFTextStack()) {
917 /* Values from the NewGRF text stack are only copied to the normal
918 * argv array at the time they are encountered. That means that if
919 * another string command references a value later in the string it
920 * would fail. We solve that by running FormatString twice. The first
921 * pass makes sure the argv array is correctly filled and the second
922 * pass can reference later values without problems. */
923 struct TextRefStack *backup = CreateTextRefStackBackup();
924 FormatString(dry_run_builder, str_arg, args, case_index, game_script, true);
925 RestoreTextRefStackBackup(backup);
926 } else {
927 FormatString(dry_run_builder, str_arg, args, case_index, game_script, true);
929 /* We have to restore the original offset here to to read the correct values. */
930 args.SetOffset(orig_offset);
932 char32_t b = '\0';
933 uint next_substr_case_index = 0;
934 std::stack<const char *, std::vector<const char *>> str_stack;
935 str_stack.push(str_arg);
937 for (;;) {
938 try {
939 while (!str_stack.empty() && (b = Utf8Consume(&str_stack.top())) == '\0') {
940 str_stack.pop();
942 if (str_stack.empty()) break;
943 const char *&str = str_stack.top();
945 if (SCC_NEWGRF_FIRST <= b && b <= SCC_NEWGRF_LAST) {
946 /* We need to pass some stuff as it might be modified. */
947 StringParameters remaining = args.GetRemainingParameters();
948 b = RemapNewGRFStringControlCode(b, &str, remaining, dry_run);
949 if (b == 0) continue;
952 if (b < SCC_CONTROL_START || b > SCC_CONTROL_END) {
953 builder.Utf8Encode(b);
954 continue;
957 args.SetTypeOfNextParameter(b);
958 switch (b) {
959 case SCC_ENCODED: {
960 ArrayStringParameters<20> sub_args;
962 char *p;
963 uint32_t stringid = std::strtoul(str, &p, 16);
964 if (*p != ':' && *p != '\0') {
965 while (*p != '\0') p++;
966 str = p;
967 builder += "(invalid SCC_ENCODED)";
968 break;
970 if (stringid >= TAB_SIZE_GAMESCRIPT) {
971 while (*p != '\0') p++;
972 str = p;
973 builder += "(invalid StringID)";
974 break;
977 int i = 0;
978 while (*p != '\0' && i < 20) {
979 uint64_t param;
980 const char *s = ++p;
982 /* Find the next value */
983 bool instring = false;
984 bool escape = false;
985 for (;; p++) {
986 if (*p == '\\') {
987 escape = true;
988 continue;
990 if (*p == '"' && escape) {
991 escape = false;
992 continue;
994 escape = false;
996 if (*p == '"') {
997 instring = !instring;
998 continue;
1000 if (instring) {
1001 continue;
1004 if (*p == ':') break;
1005 if (*p == '\0') break;
1008 if (*s != '"') {
1009 /* Check if we want to look up another string */
1010 char32_t l;
1011 size_t len = Utf8Decode(&l, s);
1012 bool lookup = (l == SCC_ENCODED);
1013 if (lookup) s += len;
1015 param = std::strtoull(s, &p, 16);
1017 if (lookup) {
1018 if (param >= TAB_SIZE_GAMESCRIPT) {
1019 while (*p != '\0') p++;
1020 str = p;
1021 builder += "(invalid sub-StringID)";
1022 break;
1024 param = MakeStringID(TEXT_TAB_GAMESCRIPT_START, param);
1027 sub_args.SetParam(i++, param);
1028 } else {
1029 s++; // skip the leading \"
1030 sub_args.SetParam(i++, std::string(s, p - s - 1)); // also skip the trailing \".
1033 /* If we didn't error out, we can actually print the string. */
1034 if (*str != '\0') {
1035 str = p;
1036 GetStringWithArgs(builder, MakeStringID(TEXT_TAB_GAMESCRIPT_START, stringid), sub_args, true);
1038 break;
1041 case SCC_NEWGRF_STRINL: {
1042 StringID substr = Utf8Consume(&str);
1043 str_stack.push(GetStringPtr(substr));
1044 break;
1047 case SCC_NEWGRF_PRINT_WORD_STRING_ID: {
1048 StringID substr = args.GetNextParameter<StringID>();
1049 str_stack.push(GetStringPtr(substr));
1050 case_index = next_substr_case_index;
1051 next_substr_case_index = 0;
1052 break;
1056 case SCC_GENDER_LIST: { // {G 0 Der Die Das}
1057 /* First read the meta data from the language file. */
1058 size_t offset = orig_offset + (uint8_t)*str++;
1059 int gender = 0;
1060 if (!dry_run && args.GetTypeAtOffset(offset) != 0) {
1061 /* Now we need to figure out what text to resolve, i.e.
1062 * what do we need to draw? So get the actual raw string
1063 * first using the control code to get said string. */
1064 char input[4 + 1];
1065 char *p = input + Utf8Encode(input, args.GetTypeAtOffset(offset));
1066 *p = '\0';
1068 /* The gender is stored at the start of the formatted string. */
1069 bool old_sgd = _scan_for_gender_data;
1070 _scan_for_gender_data = true;
1071 std::string buffer;
1072 StringBuilder tmp_builder(buffer);
1073 StringParameters tmp_params = args.GetRemainingParameters(offset);
1074 FormatString(tmp_builder, input, tmp_params);
1075 _scan_for_gender_data = old_sgd;
1077 /* And determine the string. */
1078 const char *s = buffer.c_str();
1079 char32_t c = Utf8Consume(&s);
1080 /* Does this string have a gender, if so, set it */
1081 if (c == SCC_GENDER_INDEX) gender = (uint8_t)s[0];
1083 str = ParseStringChoice(str, gender, builder);
1084 break;
1087 /* This sets up the gender for the string.
1088 * We just ignore this one. It's used in {G 0 Der Die Das} to determine the case. */
1089 case SCC_GENDER_INDEX: // {GENDER 0}
1090 if (_scan_for_gender_data) {
1091 builder.Utf8Encode(SCC_GENDER_INDEX);
1092 builder += *str++;
1093 } else {
1094 str++;
1096 break;
1098 case SCC_PLURAL_LIST: { // {P}
1099 int plural_form = *str++; // contains the plural form for this string
1100 size_t offset = orig_offset + (uint8_t)*str++;
1101 const uint64_t *v = std::get_if<uint64_t>(&args.GetParam(offset)); // contains the number that determines plural
1102 if (v != nullptr) {
1103 str = ParseStringChoice(str, DeterminePluralForm(static_cast<int64_t>(*v), plural_form), builder);
1104 } else {
1105 builder += "(invalid PLURAL parameter)";
1107 break;
1110 case SCC_ARG_INDEX: { // Move argument pointer
1111 args.SetOffset(orig_offset + (uint8_t)*str++);
1112 break;
1115 case SCC_SET_CASE: { // {SET_CASE}
1116 /* This is a pseudo command, it's outputted when someone does {STRING.ack}
1117 * The modifier is added to all subsequent GetStringWithArgs that accept the modifier. */
1118 next_substr_case_index = (uint8_t)*str++;
1119 break;
1122 case SCC_SWITCH_CASE: { // {Used to implement case switching}
1123 /* <0x9E> <NUM CASES> <CASE1> <LEN1> <STRING1> <CASE2> <LEN2> <STRING2> <CASE3> <LEN3> <STRING3> <STRINGDEFAULT>
1124 * Each LEN is printed using 2 bytes in big endian order. */
1125 uint num = (uint8_t)*str++;
1126 while (num) {
1127 if ((uint8_t)str[0] == case_index) {
1128 /* Found the case, adjust str pointer and continue */
1129 str += 3;
1130 break;
1132 /* Otherwise skip to the next case */
1133 str += 3 + (str[1] << 8) + str[2];
1134 num--;
1136 break;
1139 case SCC_REVISION: // {REV}
1140 builder += _openttd_revision;
1141 break;
1143 case SCC_RAW_STRING_POINTER: { // {RAW_STRING}
1144 const char *raw_string = args.GetNextParameterString();
1145 /* raw_string can be nullptr. */
1146 if (raw_string == nullptr) {
1147 builder += "(invalid RAW_STRING parameter)";
1148 break;
1150 FormatString(builder, raw_string, args);
1151 break;
1154 case SCC_STRING: {// {STRING}
1155 StringID string_id = args.GetNextParameter<StringID>();
1156 if (game_script && GetStringTab(string_id) != TEXT_TAB_GAMESCRIPT_START) break;
1157 /* It's prohibited for the included string to consume any arguments. */
1158 StringParameters tmp_params(args, game_script ? args.GetDataLeft() : 0);
1159 GetStringWithArgs(builder, string_id, tmp_params, next_substr_case_index, game_script);
1160 next_substr_case_index = 0;
1161 break;
1164 case SCC_STRING1:
1165 case SCC_STRING2:
1166 case SCC_STRING3:
1167 case SCC_STRING4:
1168 case SCC_STRING5:
1169 case SCC_STRING6:
1170 case SCC_STRING7: { // {STRING1..7}
1171 /* Strings that consume arguments */
1172 StringID string_id = args.GetNextParameter<StringID>();
1173 if (game_script && GetStringTab(string_id) != TEXT_TAB_GAMESCRIPT_START) break;
1174 uint size = b - SCC_STRING1 + 1;
1175 if (game_script && size > args.GetDataLeft()) {
1176 builder += "(too many parameters)";
1177 } else {
1178 StringParameters sub_args(args, game_script ? args.GetDataLeft() : size);
1179 GetStringWithArgs(builder, string_id, sub_args, next_substr_case_index, game_script);
1180 args.AdvanceOffset(size);
1182 next_substr_case_index = 0;
1183 break;
1186 case SCC_COMMA: // {COMMA}
1187 FormatCommaNumber(builder, args.GetNextParameter<int64_t>());
1188 break;
1190 case SCC_DECIMAL: { // {DECIMAL}
1191 int64_t number = args.GetNextParameter<int64_t>();
1192 int digits = args.GetNextParameter<int>();
1193 if (digits == 0) {
1194 FormatCommaNumber(builder, number);
1195 break;
1198 int64_t divisor = PowerOfTen(digits);
1199 int64_t fractional = number % divisor;
1200 number /= divisor;
1201 FormatCommaNumber(builder, number);
1202 fmt::format_to(builder, "{}{:0{}d}", GetDecimalSeparator(), fractional, digits);
1203 break;
1206 case SCC_NUM: // {NUM}
1207 FormatNoCommaNumber(builder, args.GetNextParameter<int64_t>());
1208 break;
1210 case SCC_ZEROFILL_NUM: { // {ZEROFILL_NUM}
1211 int64_t num = args.GetNextParameter<int64_t>();
1212 FormatZerofillNumber(builder, num, args.GetNextParameter<int>());
1213 break;
1216 case SCC_HEX: // {HEX}
1217 FormatHexNumber(builder, args.GetNextParameter<uint64_t>());
1218 break;
1220 case SCC_BYTES: // {BYTES}
1221 FormatBytes(builder, args.GetNextParameter<int64_t>());
1222 break;
1224 case SCC_CARGO_TINY: { // {CARGO_TINY}
1225 /* Tiny description of cargotypes. Layout:
1226 * param 1: cargo type
1227 * param 2: cargo count */
1228 CargoID cargo = args.GetNextParameter<CargoID>();
1229 if (cargo >= CargoSpec::GetArraySize()) break;
1231 StringID cargo_str = CargoSpec::Get(cargo)->units_volume;
1232 int64_t amount = 0;
1233 switch (cargo_str) {
1234 case STR_TONS:
1235 amount = _units_weight[_settings_game.locale.units_weight].c.ToDisplay(args.GetNextParameter<int64_t>());
1236 break;
1238 case STR_LITERS:
1239 amount = _units_volume[_settings_game.locale.units_volume].c.ToDisplay(args.GetNextParameter<int64_t>());
1240 break;
1242 default: {
1243 amount = args.GetNextParameter<int64_t>();
1244 break;
1248 FormatCommaNumber(builder, amount);
1249 break;
1252 case SCC_CARGO_SHORT: { // {CARGO_SHORT}
1253 /* Short description of cargotypes. Layout:
1254 * param 1: cargo type
1255 * param 2: cargo count */
1256 CargoID cargo = args.GetNextParameter<CargoID>();
1257 if (cargo >= CargoSpec::GetArraySize()) break;
1259 StringID cargo_str = CargoSpec::Get(cargo)->units_volume;
1260 switch (cargo_str) {
1261 case STR_TONS: {
1262 assert(_settings_game.locale.units_weight < lengthof(_units_weight));
1263 const auto &x = _units_weight[_settings_game.locale.units_weight];
1264 auto tmp_params = MakeParameters(x.c.ToDisplay(args.GetNextParameter<int64_t>()), x.decimal_places);
1265 FormatString(builder, GetStringPtr(x.l), tmp_params);
1266 break;
1269 case STR_LITERS: {
1270 assert(_settings_game.locale.units_volume < lengthof(_units_volume));
1271 const auto &x = _units_volume[_settings_game.locale.units_volume];
1272 auto tmp_params = MakeParameters(x.c.ToDisplay(args.GetNextParameter<int64_t>()), x.decimal_places);
1273 FormatString(builder, GetStringPtr(x.l), tmp_params);
1274 break;
1277 default: {
1278 auto tmp_params = MakeParameters(args.GetNextParameter<int64_t>());
1279 GetStringWithArgs(builder, cargo_str, tmp_params);
1280 break;
1283 break;
1286 case SCC_CARGO_LONG: { // {CARGO_LONG}
1287 /* First parameter is cargo type, second parameter is cargo count */
1288 CargoID cargo = args.GetNextParameter<CargoID>();
1289 if (IsValidCargoID(cargo) && cargo >= CargoSpec::GetArraySize()) break;
1291 StringID cargo_str = !IsValidCargoID(cargo) ? STR_QUANTITY_N_A : CargoSpec::Get(cargo)->quantifier;
1292 auto tmp_args = MakeParameters(args.GetNextParameter<int64_t>());
1293 GetStringWithArgs(builder, cargo_str, tmp_args);
1294 break;
1297 case SCC_CARGO_LIST: { // {CARGO_LIST}
1298 CargoTypes cmask = args.GetNextParameter<CargoTypes>();
1299 bool first = true;
1301 for (const auto &cs : _sorted_cargo_specs) {
1302 if (!HasBit(cmask, cs->Index())) continue;
1304 if (first) {
1305 first = false;
1306 } else {
1307 /* Add a comma if this is not the first item */
1308 builder += ", ";
1311 GetStringWithArgs(builder, cs->name, args, next_substr_case_index, game_script);
1314 /* If first is still true then no cargo is accepted */
1315 if (first) GetStringWithArgs(builder, STR_JUST_NOTHING, args, next_substr_case_index, game_script);
1317 next_substr_case_index = 0;
1318 break;
1321 case SCC_CURRENCY_SHORT: // {CURRENCY_SHORT}
1322 FormatGenericCurrency(builder, &GetCurrency(), args.GetNextParameter<int64_t>(), true);
1323 break;
1325 case SCC_CURRENCY_LONG: // {CURRENCY_LONG}
1326 FormatGenericCurrency(builder, &GetCurrency(), args.GetNextParameter<int64_t>(), false);
1327 break;
1329 case SCC_DATE_TINY: // {DATE_TINY}
1330 FormatTinyOrISODate(builder, args.GetNextParameter<TimerGameCalendar::Date>(), STR_FORMAT_DATE_TINY);
1331 break;
1333 case SCC_DATE_SHORT: // {DATE_SHORT}
1334 FormatMonthAndYear(builder, args.GetNextParameter<TimerGameCalendar::Date>(), next_substr_case_index);
1335 next_substr_case_index = 0;
1336 break;
1338 case SCC_DATE_LONG: // {DATE_LONG}
1339 FormatYmdString(builder, args.GetNextParameter<TimerGameCalendar::Date>(), next_substr_case_index);
1340 next_substr_case_index = 0;
1341 break;
1343 case SCC_DATE_ISO: // {DATE_ISO}
1344 FormatTinyOrISODate(builder, args.GetNextParameter<TimerGameCalendar::Date>(), STR_FORMAT_DATE_ISO);
1345 break;
1347 case SCC_FORCE: { // {FORCE}
1348 assert(_settings_game.locale.units_force < lengthof(_units_force));
1349 const auto &x = _units_force[_settings_game.locale.units_force];
1350 auto tmp_params = MakeParameters(x.c.ToDisplay(args.GetNextParameter<int64_t>()), x.decimal_places);
1351 FormatString(builder, GetStringPtr(x.s), tmp_params);
1352 break;
1355 case SCC_HEIGHT: { // {HEIGHT}
1356 assert(_settings_game.locale.units_height < lengthof(_units_height));
1357 const auto &x = _units_height[_settings_game.locale.units_height];
1358 auto tmp_params = MakeParameters(x.c.ToDisplay(args.GetNextParameter<int64_t>()), x.decimal_places);
1359 FormatString(builder, GetStringPtr(x.s), tmp_params);
1360 break;
1363 case SCC_POWER: { // {POWER}
1364 assert(_settings_game.locale.units_power < lengthof(_units_power));
1365 const auto &x = _units_power[_settings_game.locale.units_power];
1366 auto tmp_params = MakeParameters(x.c.ToDisplay(args.GetNextParameter<int64_t>()), x.decimal_places);
1367 FormatString(builder, GetStringPtr(x.s), tmp_params);
1368 break;
1371 case SCC_POWER_TO_WEIGHT: { // {POWER_TO_WEIGHT}
1372 auto setting = _settings_game.locale.units_power * 3u + _settings_game.locale.units_weight;
1373 assert(setting < lengthof(_units_power_to_weight));
1374 const auto &x = _units_power_to_weight[setting];
1375 auto tmp_params = MakeParameters(x.c.ToDisplay(args.GetNextParameter<int64_t>()), x.decimal_places);
1376 FormatString(builder, GetStringPtr(x.s), tmp_params);
1377 break;
1380 case SCC_VELOCITY: { // {VELOCITY}
1381 int64_t arg = args.GetNextParameter<int64_t>();
1382 // Unpack vehicle type from packed argument to get desired units.
1383 VehicleType vt = static_cast<VehicleType>(GB(arg, 56, 8));
1384 const auto &x = GetVelocityUnits(vt);
1385 auto tmp_params = MakeParameters(ConvertKmhishSpeedToDisplaySpeed(GB(arg, 0, 56), vt), x.decimal_places);
1386 FormatString(builder, GetStringPtr(x.s), tmp_params);
1387 break;
1390 case SCC_VOLUME_SHORT: { // {VOLUME_SHORT}
1391 assert(_settings_game.locale.units_volume < lengthof(_units_volume));
1392 const auto &x = _units_volume[_settings_game.locale.units_volume];
1393 auto tmp_params = MakeParameters(x.c.ToDisplay(args.GetNextParameter<int64_t>()), x.decimal_places);
1394 FormatString(builder, GetStringPtr(x.s), tmp_params);
1395 break;
1398 case SCC_VOLUME_LONG: { // {VOLUME_LONG}
1399 assert(_settings_game.locale.units_volume < lengthof(_units_volume));
1400 const auto &x = _units_volume[_settings_game.locale.units_volume];
1401 auto tmp_params = MakeParameters(x.c.ToDisplay(args.GetNextParameter<int64_t>()), x.decimal_places);
1402 FormatString(builder, GetStringPtr(x.l), tmp_params);
1403 break;
1406 case SCC_WEIGHT_SHORT: { // {WEIGHT_SHORT}
1407 assert(_settings_game.locale.units_weight < lengthof(_units_weight));
1408 const auto &x = _units_weight[_settings_game.locale.units_weight];
1409 auto tmp_params = MakeParameters(x.c.ToDisplay(args.GetNextParameter<int64_t>()), x.decimal_places);
1410 FormatString(builder, GetStringPtr(x.s), tmp_params);
1411 break;
1414 case SCC_WEIGHT_LONG: { // {WEIGHT_LONG}
1415 assert(_settings_game.locale.units_weight < lengthof(_units_weight));
1416 const auto &x = _units_weight[_settings_game.locale.units_weight];
1417 auto tmp_params = MakeParameters(x.c.ToDisplay(args.GetNextParameter<int64_t>()), x.decimal_places);
1418 FormatString(builder, GetStringPtr(x.l), tmp_params);
1419 break;
1422 case SCC_UNITS_DAYS_OR_SECONDS: { // {UNITS_DAYS_OR_SECONDS}
1423 uint8_t realtime = TimerGameEconomy::UsingWallclockUnits(_game_mode == GM_MENU);
1424 const auto &x = _units_time_days_or_seconds[realtime];
1425 auto tmp_params = MakeParameters(x.c.ToDisplay(args.GetNextParameter<int64_t>()), x.decimal_places);
1426 FormatString(builder, GetStringPtr(x.s), tmp_params);
1427 break;
1430 case SCC_UNITS_MONTHS_OR_MINUTES: { // {UNITS_MONTHS_OR_MINUTES}
1431 uint8_t realtime = TimerGameEconomy::UsingWallclockUnits(_game_mode == GM_MENU);
1432 const auto &x = _units_time_months_or_minutes[realtime];
1433 auto tmp_params = MakeParameters(x.c.ToDisplay(args.GetNextParameter<int64_t>()), x.decimal_places);
1434 FormatString(builder, GetStringPtr(x.s), tmp_params);
1435 break;
1438 case SCC_UNITS_YEARS_OR_PERIODS: { // {UNITS_YEARS_OR_PERIODS}
1439 uint8_t realtime = TimerGameEconomy::UsingWallclockUnits(_game_mode == GM_MENU);
1440 const auto &x = _units_time_years_or_periods[realtime];
1441 auto tmp_params = MakeParameters(x.c.ToDisplay(args.GetNextParameter<int64_t>()), x.decimal_places);
1442 FormatString(builder, GetStringPtr(x.s), tmp_params);
1443 break;
1446 case SCC_UNITS_YEARS_OR_MINUTES: { // {UNITS_YEARS_OR_MINUTES}
1447 uint8_t realtime = TimerGameEconomy::UsingWallclockUnits(_game_mode == GM_MENU);
1448 const auto &x = _units_time_years_or_minutes[realtime];
1449 auto tmp_params = MakeParameters(x.c.ToDisplay(args.GetNextParameter<int64_t>()), x.decimal_places);
1450 FormatString(builder, GetStringPtr(x.s), tmp_params);
1451 break;
1454 case SCC_COMPANY_NAME: { // {COMPANY}
1455 const Company *c = Company::GetIfValid(args.GetNextParameter<CompanyID>());
1456 if (c == nullptr) break;
1458 if (!c->name.empty()) {
1459 auto tmp_params = MakeParameters(c->name);
1460 GetStringWithArgs(builder, STR_JUST_RAW_STRING, tmp_params);
1461 } else {
1462 auto tmp_params = MakeParameters(c->name_2);
1463 GetStringWithArgs(builder, c->name_1, tmp_params);
1465 break;
1468 case SCC_COMPANY_NUM: { // {COMPANY_NUM}
1469 CompanyID company = args.GetNextParameter<CompanyID>();
1471 /* Nothing is added for AI or inactive companies */
1472 if (Company::IsValidHumanID(company)) {
1473 auto tmp_params = MakeParameters(company + 1);
1474 GetStringWithArgs(builder, STR_FORMAT_COMPANY_NUM, tmp_params);
1476 break;
1479 case SCC_DEPOT_NAME: { // {DEPOT}
1480 VehicleType vt = args.GetNextParameter<VehicleType>();
1481 if (vt == VEH_AIRCRAFT) {
1482 auto tmp_params = MakeParameters(args.GetNextParameter<StationID>());
1483 GetStringWithArgs(builder, STR_FORMAT_DEPOT_NAME_AIRCRAFT, tmp_params);
1484 break;
1487 const Depot *d = Depot::Get(args.GetNextParameter<DepotID>());
1488 if (!d->name.empty()) {
1489 auto tmp_params = MakeParameters(d->name);
1490 GetStringWithArgs(builder, STR_JUST_RAW_STRING, tmp_params);
1491 } else {
1492 auto tmp_params = MakeParameters(d->town->index, d->town_cn + 1);
1493 GetStringWithArgs(builder, STR_FORMAT_DEPOT_NAME_TRAIN + 2 * vt + (d->town_cn == 0 ? 0 : 1), tmp_params);
1495 break;
1498 case SCC_ENGINE_NAME: { // {ENGINE}
1499 int64_t arg = args.GetNextParameter<int64_t>();
1500 const Engine *e = Engine::GetIfValid(static_cast<EngineID>(arg));
1501 if (e == nullptr) break;
1503 if (!e->name.empty() && e->IsEnabled()) {
1504 auto tmp_params = MakeParameters(e->name);
1505 GetStringWithArgs(builder, STR_JUST_RAW_STRING, tmp_params);
1506 break;
1509 if (HasBit(e->info.callback_mask, CBM_VEHICLE_NAME)) {
1510 uint16_t callback = GetVehicleCallback(CBID_VEHICLE_NAME, static_cast<uint32_t>(arg >> 32), 0, e->index, nullptr);
1511 /* Not calling ErrorUnknownCallbackResult due to being inside string processing. */
1512 if (callback != CALLBACK_FAILED && callback < 0x400) {
1513 const GRFFile *grffile = e->GetGRF();
1514 assert(grffile != nullptr);
1516 StartTextRefStackUsage(grffile, 6);
1517 ArrayStringParameters<6> tmp_params;
1518 GetStringWithArgs(builder, GetGRFStringID(grffile->grfid, 0xD000 + callback), tmp_params);
1519 StopTextRefStackUsage();
1521 break;
1525 auto tmp_params = ArrayStringParameters<0>();
1526 GetStringWithArgs(builder, e->info.string_id, tmp_params);
1527 break;
1530 case SCC_GROUP_NAME: { // {GROUP}
1531 const Group *g = Group::GetIfValid(args.GetNextParameter<GroupID>());
1532 if (g == nullptr) break;
1534 if (!g->name.empty()) {
1535 auto tmp_params = MakeParameters(g->name);
1536 GetStringWithArgs(builder, STR_JUST_RAW_STRING, tmp_params);
1537 } else {
1538 auto tmp_params = MakeParameters(g->number);
1539 GetStringWithArgs(builder, STR_FORMAT_GROUP_NAME, tmp_params);
1541 break;
1544 case SCC_INDUSTRY_NAME: { // {INDUSTRY}
1545 const Industry *i = Industry::GetIfValid(args.GetNextParameter<IndustryID>());
1546 if (i == nullptr) break;
1548 static bool use_cache = true;
1549 if (_scan_for_gender_data) {
1550 /* Gender is defined by the industry type.
1551 * STR_FORMAT_INDUSTRY_NAME may have the town first, so it would result in the gender of the town name */
1552 auto tmp_params = ArrayStringParameters<0>();
1553 FormatString(builder, GetStringPtr(GetIndustrySpec(i->type)->name), tmp_params, next_substr_case_index);
1554 } else if (use_cache) { // Use cached version if first call
1555 AutoRestoreBackup cache_backup(use_cache, false);
1556 builder += i->GetCachedName();
1557 } else {
1558 /* First print the town name and the industry type name. */
1559 auto tmp_params = MakeParameters(i->town->index, GetIndustrySpec(i->type)->name);
1560 FormatString(builder, GetStringPtr(STR_FORMAT_INDUSTRY_NAME), tmp_params, next_substr_case_index);
1562 next_substr_case_index = 0;
1563 break;
1566 case SCC_PRESIDENT_NAME: { // {PRESIDENT_NAME}
1567 const Company *c = Company::GetIfValid(args.GetNextParameter<CompanyID>());
1568 if (c == nullptr) break;
1570 if (!c->president_name.empty()) {
1571 auto tmp_params = MakeParameters(c->president_name);
1572 GetStringWithArgs(builder, STR_JUST_RAW_STRING, tmp_params);
1573 } else {
1574 auto tmp_params = MakeParameters(c->president_name_2);
1575 GetStringWithArgs(builder, c->president_name_1, tmp_params);
1577 break;
1580 case SCC_STATION_NAME: { // {STATION}
1581 StationID sid = args.GetNextParameter<StationID>();
1582 const Station *st = Station::GetIfValid(sid);
1584 if (st == nullptr) {
1585 /* The station doesn't exist anymore. The only place where we might
1586 * be "drawing" an invalid station is in the case of cargo that is
1587 * in transit. */
1588 auto tmp_params = ArrayStringParameters<0>();
1589 GetStringWithArgs(builder, STR_UNKNOWN_STATION, tmp_params);
1590 break;
1593 static bool use_cache = true;
1594 if (use_cache) { // Use cached version if first call
1595 AutoRestoreBackup cache_backup(use_cache, false);
1596 builder += st->GetCachedName();
1597 } else if (!st->name.empty()) {
1598 auto tmp_params = MakeParameters(st->name);
1599 GetStringWithArgs(builder, STR_JUST_RAW_STRING, tmp_params);
1600 } else {
1601 StringID string_id = st->string_id;
1602 if (st->indtype != IT_INVALID) {
1603 /* Special case where the industry provides the name for the station */
1604 const IndustrySpec *indsp = GetIndustrySpec(st->indtype);
1606 /* Industry GRFs can change which might remove the station name and
1607 * thus cause very strange things. Here we check for that before we
1608 * actually set the station name. */
1609 if (indsp->station_name != STR_NULL && indsp->station_name != STR_UNDEFINED) {
1610 string_id = indsp->station_name;
1614 auto tmp_params = MakeParameters(STR_TOWN_NAME, st->town->index, st->index);
1615 GetStringWithArgs(builder, string_id, tmp_params);
1617 break;
1620 case SCC_TOWN_NAME: { // {TOWN}
1621 const Town *t = Town::GetIfValid(args.GetNextParameter<TownID>());
1622 if (t == nullptr) break;
1624 static bool use_cache = true;
1625 if (use_cache) { // Use cached version if first call
1626 AutoRestoreBackup cache_backup(use_cache, false);
1627 builder += t->GetCachedName();
1628 } else if (!t->name.empty()) {
1629 auto tmp_params = MakeParameters(t->name);
1630 GetStringWithArgs(builder, STR_JUST_RAW_STRING, tmp_params);
1631 } else {
1632 GetTownName(builder, t);
1634 break;
1637 case SCC_WAYPOINT_NAME: { // {WAYPOINT}
1638 Waypoint *wp = Waypoint::GetIfValid(args.GetNextParameter<StationID>());
1639 if (wp == nullptr) break;
1641 if (!wp->name.empty()) {
1642 auto tmp_params = MakeParameters(wp->name);
1643 GetStringWithArgs(builder, STR_JUST_RAW_STRING, tmp_params);
1644 } else {
1645 auto tmp_params = MakeParameters(wp->town->index, wp->town_cn + 1);
1646 StringID string_id = ((wp->string_id == STR_SV_STNAME_BUOY) ? STR_FORMAT_BUOY_NAME : STR_FORMAT_WAYPOINT_NAME);
1647 if (wp->town_cn != 0) string_id++;
1648 GetStringWithArgs(builder, string_id, tmp_params);
1650 break;
1653 case SCC_VEHICLE_NAME: { // {VEHICLE}
1654 const Vehicle *v = Vehicle::GetIfValid(args.GetNextParameter<VehicleID>());
1655 if (v == nullptr) break;
1657 if (!v->name.empty()) {
1658 auto tmp_params = MakeParameters(v->name);
1659 GetStringWithArgs(builder, STR_JUST_RAW_STRING, tmp_params);
1660 } else if (v->group_id != DEFAULT_GROUP) {
1661 /* The vehicle has no name, but is member of a group, so print group name */
1662 auto tmp_params = MakeParameters(v->group_id, v->unitnumber);
1663 GetStringWithArgs(builder, STR_FORMAT_GROUP_VEHICLE_NAME, tmp_params);
1664 } else {
1665 auto tmp_params = MakeParameters(v->unitnumber);
1667 StringID string_id;
1668 switch (v->type) {
1669 default: string_id = STR_INVALID_VEHICLE; break;
1670 case VEH_TRAIN: string_id = STR_SV_TRAIN_NAME; break;
1671 case VEH_ROAD: string_id = STR_SV_ROAD_VEHICLE_NAME; break;
1672 case VEH_SHIP: string_id = STR_SV_SHIP_NAME; break;
1673 case VEH_AIRCRAFT: string_id = STR_SV_AIRCRAFT_NAME; break;
1676 GetStringWithArgs(builder, string_id, tmp_params);
1678 break;
1681 case SCC_SIGN_NAME: { // {SIGN}
1682 const Sign *si = Sign::GetIfValid(args.GetNextParameter<SignID>());
1683 if (si == nullptr) break;
1685 if (!si->name.empty()) {
1686 auto tmp_params = MakeParameters(si->name);
1687 GetStringWithArgs(builder, STR_JUST_RAW_STRING, tmp_params);
1688 } else {
1689 auto tmp_params = ArrayStringParameters<0>();
1690 GetStringWithArgs(builder, STR_DEFAULT_SIGN_NAME, tmp_params);
1692 break;
1695 case SCC_STATION_FEATURES: { // {STATIONFEATURES}
1696 StationGetSpecialString(builder, args.GetNextParameter<StationFacility>());
1697 break;
1700 case SCC_COLOUR: { // {COLOUR}
1701 StringControlCode scc = (StringControlCode)(SCC_BLUE + args.GetNextParameter<Colours>());
1702 if (IsInsideMM(scc, SCC_BLUE, SCC_COLOUR)) builder.Utf8Encode(scc);
1703 break;
1706 default:
1707 builder.Utf8Encode(b);
1708 break;
1710 } catch (std::out_of_range &e) {
1711 Debug(misc, 0, "FormatString: {}", e.what());
1712 builder += "(invalid parameter)";
1718 static void StationGetSpecialString(StringBuilder &builder, StationFacility x)
1720 if ((x & FACIL_TRAIN) != 0) builder.Utf8Encode(SCC_TRAIN);
1721 if ((x & FACIL_TRUCK_STOP) != 0) builder.Utf8Encode(SCC_LORRY);
1722 if ((x & FACIL_BUS_STOP) != 0) builder.Utf8Encode(SCC_BUS);
1723 if ((x & FACIL_DOCK) != 0) builder.Utf8Encode(SCC_SHIP);
1724 if ((x & FACIL_AIRPORT) != 0) builder.Utf8Encode(SCC_PLANE);
1727 static void GetSpecialTownNameString(StringBuilder &builder, int ind, uint32_t seed)
1729 GenerateTownNameString(builder, ind, seed);
1732 static const char * const _silly_company_names[] = {
1733 "Bloggs Brothers",
1734 "Tiny Transport Ltd.",
1735 "Express Travel",
1736 "Comfy-Coach & Co.",
1737 "Crush & Bump Ltd.",
1738 "Broken & Late Ltd.",
1739 "Sam Speedy & Son",
1740 "Supersonic Travel",
1741 "Mike's Motors",
1742 "Lightning International",
1743 "Pannik & Loozit Ltd.",
1744 "Inter-City Transport",
1745 "Getout & Pushit Ltd."
1748 static const char * const _surname_list[] = {
1749 "Adams",
1750 "Allan",
1751 "Baker",
1752 "Bigwig",
1753 "Black",
1754 "Bloggs",
1755 "Brown",
1756 "Campbell",
1757 "Gordon",
1758 "Hamilton",
1759 "Hawthorn",
1760 "Higgins",
1761 "Green",
1762 "Gribble",
1763 "Jones",
1764 "McAlpine",
1765 "MacDonald",
1766 "McIntosh",
1767 "Muir",
1768 "Murphy",
1769 "Nelson",
1770 "O'Donnell",
1771 "Parker",
1772 "Phillips",
1773 "Pilkington",
1774 "Quigley",
1775 "Sharkey",
1776 "Thomson",
1777 "Watkins"
1780 static const char * const _silly_surname_list[] = {
1781 "Grumpy",
1782 "Dozy",
1783 "Speedy",
1784 "Nosey",
1785 "Dribble",
1786 "Mushroom",
1787 "Cabbage",
1788 "Sniffle",
1789 "Fishy",
1790 "Swindle",
1791 "Sneaky",
1792 "Nutkins"
1795 static const char _initial_name_letters[] = {
1796 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J',
1797 'K', 'L', 'M', 'N', 'P', 'R', 'S', 'T', 'W',
1800 static std::span<const char * const> GetSurnameOptions()
1802 if (_settings_game.game_creation.landscape == LT_TOYLAND) return _silly_surname_list;
1803 return _surname_list;
1807 * Get the surname of the president with the given seed.
1808 * @param seed The seed the surname was generated from.
1809 * @return The surname.
1811 static const char *GetSurname(uint32_t seed)
1813 auto surname_options = GetSurnameOptions();
1814 return surname_options[surname_options.size() * GB(seed, 16, 8) >> 8];
1817 static void GenAndCoName(StringBuilder &builder, uint32_t seed)
1819 builder += GetSurname(seed);
1820 builder += " & Co.";
1823 static void GenPresidentName(StringBuilder &builder, uint32_t seed)
1825 builder += _initial_name_letters[std::size(_initial_name_letters) * GB(seed, 0, 8) >> 8];
1826 builder += ". ";
1828 /* The second initial is optional. */
1829 size_t index = (std::size(_initial_name_letters) + 35) * GB(seed, 8, 8) >> 8;
1830 if (index < std::size(_initial_name_letters)) {
1831 builder += _initial_name_letters[index];
1832 builder += ". ";
1835 builder += GetSurname(seed);
1838 static void GetSpecialNameString(StringBuilder &builder, int ind, StringParameters &args)
1840 switch (ind) {
1841 case 1: // not used
1842 builder += _silly_company_names[std::min<size_t>(args.GetNextParameter<uint16_t>(), std::size(_silly_company_names) - 1)];
1843 return;
1845 case 2: // used for Foobar & Co company names
1846 GenAndCoName(builder, args.GetNextParameter<uint32_t>());
1847 return;
1849 case 3: // President name
1850 GenPresidentName(builder, args.GetNextParameter<uint32_t>());
1851 return;
1854 /* town name? */
1855 if (IsInsideMM(ind - 6, 0, SPECSTR_TOWNNAME_LAST - SPECSTR_TOWNNAME_START + 1)) {
1856 GetSpecialTownNameString(builder, ind - 6, args.GetNextParameter<uint32_t>());
1857 builder += " Transport";
1858 return;
1861 NOT_REACHED();
1865 * Check whether the header is a valid header for OpenTTD.
1866 * @return true iff the header is deemed valid.
1868 bool LanguagePackHeader::IsValid() const
1870 return this->ident == TO_LE32(LanguagePackHeader::IDENT) &&
1871 this->version == TO_LE32(LANGUAGE_PACK_VERSION) &&
1872 this->plural_form < LANGUAGE_MAX_PLURAL &&
1873 this->text_dir <= 1 &&
1874 this->newgrflangid < MAX_LANG &&
1875 this->num_genders < MAX_NUM_GENDERS &&
1876 this->num_cases < MAX_NUM_CASES &&
1877 StrValid(this->name) &&
1878 StrValid(this->own_name) &&
1879 StrValid(this->isocode) &&
1880 StrValid(this->digit_group_separator) &&
1881 StrValid(this->digit_group_separator_currency) &&
1882 StrValid(this->digit_decimal_separator);
1886 * Check whether a translation is sufficiently finished to offer it to the public.
1888 bool LanguagePackHeader::IsReasonablyFinished() const
1890 /* "Less than 25% missing" is "sufficiently finished". */
1891 return 4 * this->missing < LANGUAGE_TOTAL_STRINGS;
1895 * Read a particular language.
1896 * @param lang The metadata about the language.
1897 * @return Whether the loading went okay or not.
1899 bool ReadLanguagePack(const LanguageMetadata *lang)
1901 /* Current language pack */
1902 size_t len = 0;
1903 std::unique_ptr<LanguagePack, LanguagePackDeleter> lang_pack(reinterpret_cast<LanguagePack *>(ReadFileToMem(FS2OTTD(lang->file), len, 1U << 20).release()));
1904 if (!lang_pack) return false;
1906 /* End of read data (+ terminating zero added in ReadFileToMem()) */
1907 const char *end = (char *)lang_pack.get() + len + 1;
1909 /* We need at least one byte of lang_pack->data */
1910 if (end <= lang_pack->data || !lang_pack->IsValid()) {
1911 return false;
1914 std::array<uint, TEXT_TAB_END> tab_start, tab_num;
1916 uint count = 0;
1917 for (uint i = 0; i < TEXT_TAB_END; i++) {
1918 uint16_t num = FROM_LE16(lang_pack->offsets[i]);
1919 if (num > TAB_SIZE) return false;
1921 tab_start[i] = count;
1922 tab_num[i] = num;
1923 count += num;
1926 /* Allocate offsets */
1927 std::vector<char *> offs(count);
1929 /* Fill offsets */
1930 char *s = lang_pack->data;
1931 len = (uint8_t)*s++;
1932 for (uint i = 0; i < count; i++) {
1933 if (s + len >= end) return false;
1935 if (len >= 0xC0) {
1936 len = ((len & 0x3F) << 8) + (uint8_t)*s++;
1937 if (s + len >= end) return false;
1939 offs[i] = s;
1940 s += len;
1941 len = (uint8_t)*s;
1942 *s++ = '\0'; // zero terminate the string
1945 _langpack.langpack = std::move(lang_pack);
1946 _langpack.offsets = std::move(offs);
1947 _langpack.langtab_num = tab_num;
1948 _langpack.langtab_start = tab_start;
1950 _current_language = lang;
1951 _current_text_dir = (TextDirection)_current_language->text_dir;
1952 _config_language_file = FS2OTTD(_current_language->file.filename());
1953 SetCurrentGrfLangID(_current_language->newgrflangid);
1955 #ifdef _WIN32
1956 extern void Win32SetCurrentLocaleName(std::string iso_code);
1957 Win32SetCurrentLocaleName(_current_language->isocode);
1958 #endif
1960 #ifdef WITH_COCOA
1961 extern void MacOSSetCurrentLocaleName(const char *iso_code);
1962 MacOSSetCurrentLocaleName(_current_language->isocode);
1963 #endif
1965 #ifdef WITH_ICU_I18N
1966 /* Create a collator instance for our current locale. */
1967 UErrorCode status = U_ZERO_ERROR;
1968 _current_collator.reset(icu::Collator::createInstance(icu::Locale(_current_language->isocode), status));
1969 /* Sort number substrings by their numerical value. */
1970 if (_current_collator) _current_collator->setAttribute(UCOL_NUMERIC_COLLATION, UCOL_ON, status);
1971 /* Avoid using the collator if it is not correctly set. */
1972 if (U_FAILURE(status)) {
1973 _current_collator.reset();
1975 #endif /* WITH_ICU_I18N */
1977 Layouter::Initialize();
1979 /* Some lists need to be sorted again after a language change. */
1980 ReconsiderGameScriptLanguage();
1981 InitializeSortedCargoSpecs();
1982 SortIndustryTypes();
1983 BuildIndustriesLegend();
1984 BuildContentTypeStringList();
1985 InvalidateWindowClassesData(WC_BUILD_VEHICLE); // Build vehicle window.
1986 InvalidateWindowClassesData(WC_TRAINS_LIST); // Train group window.
1987 InvalidateWindowClassesData(WC_ROADVEH_LIST); // Road vehicle group window.
1988 InvalidateWindowClassesData(WC_SHIPS_LIST); // Ship group window.
1989 InvalidateWindowClassesData(WC_AIRCRAFT_LIST); // Aircraft group window.
1990 InvalidateWindowClassesData(WC_INDUSTRY_DIRECTORY); // Industry directory window.
1991 InvalidateWindowClassesData(WC_STATION_LIST); // Station list window.
1993 return true;
1996 /* Win32 implementation in win32.cpp.
1997 * OS X implementation in os/macosx/macos.mm. */
1998 #if !(defined(_WIN32) || defined(__APPLE__))
2000 * Determine the current charset based on the environment
2001 * First check some default values, after this one we passed ourselves
2002 * and if none exist return the value for $LANG
2003 * @param param environment variable to check conditionally if default ones are not
2004 * set. Pass nullptr if you don't want additional checks.
2005 * @return return string containing current charset, or nullptr if not-determinable
2007 const char *GetCurrentLocale(const char *param)
2009 const char *env;
2011 env = std::getenv("LANGUAGE");
2012 if (env != nullptr) return env;
2014 env = std::getenv("LC_ALL");
2015 if (env != nullptr) return env;
2017 if (param != nullptr) {
2018 env = std::getenv(param);
2019 if (env != nullptr) return env;
2022 return std::getenv("LANG");
2024 #else
2025 const char *GetCurrentLocale(const char *param);
2026 #endif /* !(defined(_WIN32) || defined(__APPLE__)) */
2029 * Get the language with the given NewGRF language ID.
2030 * @param newgrflangid NewGRF languages ID to check.
2031 * @return The language's metadata, or nullptr if it is not known.
2033 const LanguageMetadata *GetLanguage(uint8_t newgrflangid)
2035 for (const LanguageMetadata &lang : _languages) {
2036 if (newgrflangid == lang.newgrflangid) return &lang;
2039 return nullptr;
2043 * Reads the language file header and checks compatibility.
2044 * @param file the file to read
2045 * @param hdr the place to write the header information to
2046 * @return true if and only if the language file is of a compatible version
2048 static bool GetLanguageFileHeader(const std::string &file, LanguagePackHeader *hdr)
2050 auto f = FileHandle::Open(file, "rb");
2051 if (!f.has_value()) return false;
2053 size_t read = fread(hdr, sizeof(*hdr), 1, *f);
2055 bool ret = read == 1 && hdr->IsValid();
2057 /* Convert endianness for the windows language ID */
2058 if (ret) {
2059 hdr->missing = FROM_LE16(hdr->missing);
2060 hdr->winlangid = FROM_LE16(hdr->winlangid);
2062 return ret;
2066 * Search for the languages in the given directory and add them to the #_languages list.
2067 * @param path the base directory to search in
2069 static void FillLanguageList(const std::string &path)
2071 std::error_code error_code;
2072 for (const auto &dir_entry : std::filesystem::directory_iterator(OTTD2FS(path), error_code)) {
2073 if (!dir_entry.is_regular_file()) continue;
2074 if (dir_entry.path().extension() != ".lng") continue;
2076 LanguageMetadata lmd;
2077 lmd.file = dir_entry.path();
2079 /* Check whether the file is of the correct version */
2080 if (!GetLanguageFileHeader(FS2OTTD(lmd.file), &lmd)) {
2081 Debug(misc, 3, "{} is not a valid language file", FS2OTTD(lmd.file));
2082 } else if (GetLanguage(lmd.newgrflangid) != nullptr) {
2083 Debug(misc, 3, "{}'s language ID is already known", FS2OTTD(lmd.file));
2084 } else {
2085 _languages.push_back(lmd);
2088 if (error_code) {
2089 Debug(misc, 9, "Unable to open directory {}: {}", path, error_code.message());
2094 * Make a list of the available language packs. Put the data in
2095 * #_languages list.
2097 void InitializeLanguagePacks()
2099 for (Searchpath sp : _valid_searchpaths) {
2100 FillLanguageList(FioGetDirectory(sp, LANG_DIR));
2102 if (_languages.empty()) UserError("No available language packs (invalid versions?)");
2104 /* Acquire the locale of the current system */
2105 const char *lang = GetCurrentLocale("LC_MESSAGES");
2106 if (lang == nullptr) lang = "en_GB";
2108 const LanguageMetadata *chosen_language = nullptr; ///< Matching the language in the configuration file or the current locale
2109 const LanguageMetadata *language_fallback = nullptr; ///< Using pt_PT for pt_BR locale when pt_BR is not available
2110 const LanguageMetadata *en_GB_fallback = _languages.data(); ///< Fallback when no locale-matching language has been found
2112 /* Find a proper language. */
2113 for (const LanguageMetadata &lng : _languages) {
2114 /* We are trying to find a default language. The priority is by
2115 * configuration file, local environment and last, if nothing found,
2116 * English. */
2117 if (_config_language_file == FS2OTTD(lng.file.filename())) {
2118 chosen_language = &lng;
2119 break;
2122 if (strcmp (lng.isocode, "en_GB") == 0) en_GB_fallback = &lng;
2124 /* Only auto-pick finished translations */
2125 if (!lng.IsReasonablyFinished()) continue;
2127 if (strncmp(lng.isocode, lang, 5) == 0) chosen_language = &lng;
2128 if (strncmp(lng.isocode, lang, 2) == 0) language_fallback = &lng;
2131 /* We haven't found the language in the config nor the one in the locale.
2132 * Now we set it to one of the fallback languages */
2133 if (chosen_language == nullptr) {
2134 chosen_language = (language_fallback != nullptr) ? language_fallback : en_GB_fallback;
2137 if (!ReadLanguagePack(chosen_language)) UserError("Can't read language pack '{}'", FS2OTTD(chosen_language->file));
2141 * Get the ISO language code of the currently loaded language.
2142 * @return the ISO code.
2144 const char *GetCurrentLanguageIsoCode()
2146 return _langpack.langpack->isocode;
2150 * Check whether there are glyphs missing in the current language.
2151 * @return If glyphs are missing, return \c true, else return \c false.
2153 bool MissingGlyphSearcher::FindMissingGlyphs()
2155 InitFontCache(this->Monospace());
2157 this->Reset();
2158 for (auto text = this->NextString(); text.has_value(); text = this->NextString()) {
2159 auto src = text->cbegin();
2161 FontSize size = this->DefaultSize();
2162 FontCache *fc = FontCache::Get(size);
2163 while (src != text->cend()) {
2164 char32_t c = Utf8Consume(src);
2166 if (c >= SCC_FIRST_FONT && c <= SCC_LAST_FONT) {
2167 size = (FontSize)(c - SCC_FIRST_FONT);
2168 fc = FontCache::Get(size);
2169 } else if (!IsInsideMM(c, SCC_SPRITE_START, SCC_SPRITE_END) && IsPrintable(c) && !IsTextDirectionChar(c) && fc->MapCharToGlyph(c, false) == 0) {
2170 /* The character is printable, but not in the normal font. This is the case we were testing for. */
2171 std::string size_name;
2173 switch (size) {
2174 case FS_NORMAL: size_name = "medium"; break;
2175 case FS_SMALL: size_name = "small"; break;
2176 case FS_LARGE: size_name = "large"; break;
2177 case FS_MONO: size_name = "mono"; break;
2178 default: NOT_REACHED();
2181 Debug(fontcache, 0, "Font is missing glyphs to display char 0x{:X} in {} font size", (int)c, size_name);
2182 return true;
2186 return false;
2189 /** Helper for searching through the language pack. */
2190 class LanguagePackGlyphSearcher : public MissingGlyphSearcher {
2191 uint i; ///< Iterator for the primary language tables.
2192 uint j; ///< Iterator for the secondary language tables.
2194 void Reset() override
2196 this->i = 0;
2197 this->j = 0;
2200 FontSize DefaultSize() override
2202 return FS_NORMAL;
2205 std::optional<std::string_view> NextString() override
2207 if (this->i >= TEXT_TAB_END) return std::nullopt;
2209 const char *ret = _langpack.offsets[_langpack.langtab_start[this->i] + this->j];
2211 this->j++;
2212 while (this->i < TEXT_TAB_END && this->j >= _langpack.langtab_num[this->i]) {
2213 this->i++;
2214 this->j = 0;
2217 return ret;
2220 bool Monospace() override
2222 return false;
2225 void SetFontNames([[maybe_unused]] FontCacheSettings *settings, [[maybe_unused]] const char *font_name, [[maybe_unused]] const void *os_data) override
2227 #if defined(WITH_FREETYPE) || defined(_WIN32) || defined(WITH_COCOA)
2228 settings->small.font = font_name;
2229 settings->medium.font = font_name;
2230 settings->large.font = font_name;
2232 settings->small.os_handle = os_data;
2233 settings->medium.os_handle = os_data;
2234 settings->large.os_handle = os_data;
2235 #endif
2240 * Check whether the currently loaded language pack
2241 * uses characters that the currently loaded font
2242 * does not support. If this is the case an error
2243 * message will be shown in English. The error
2244 * message will not be localized because that would
2245 * mean it might use characters that are not in the
2246 * font, which is the whole reason this check has
2247 * been added.
2248 * @param base_font Whether to look at the base font as well.
2249 * @param searcher The methods to use to search for strings to check.
2250 * If nullptr the loaded language pack searcher is used.
2252 void CheckForMissingGlyphs(bool base_font, MissingGlyphSearcher *searcher)
2254 static LanguagePackGlyphSearcher pack_searcher;
2255 if (searcher == nullptr) searcher = &pack_searcher;
2256 bool bad_font = !base_font || searcher->FindMissingGlyphs();
2257 #if defined(WITH_FREETYPE) || defined(_WIN32) || defined(WITH_COCOA)
2258 if (bad_font) {
2259 /* We found an unprintable character... lets try whether we can find
2260 * a fallback font that can print the characters in the current language. */
2261 bool any_font_configured = !_fcsettings.medium.font.empty();
2262 FontCacheSettings backup = _fcsettings;
2264 _fcsettings.mono.os_handle = nullptr;
2265 _fcsettings.medium.os_handle = nullptr;
2267 bad_font = !SetFallbackFont(&_fcsettings, _langpack.langpack->isocode, _langpack.langpack->winlangid, searcher);
2269 _fcsettings = backup;
2271 if (!bad_font && any_font_configured) {
2272 /* If the user configured a bad font, and we found a better one,
2273 * show that we loaded the better font instead of the configured one.
2274 * The colour 'character' might change in the
2275 * future, so for safety we just Utf8 Encode it into the string,
2276 * which takes exactly three characters, so it replaces the "XXX"
2277 * with the colour marker. */
2278 static std::string err_str("XXXThe current font is missing some of the characters used in the texts for this language. Using system fallback font instead.");
2279 Utf8Encode(err_str.data(), SCC_YELLOW);
2280 SetDParamStr(0, err_str);
2281 ShowErrorMessage(STR_JUST_RAW_STRING, INVALID_STRING_ID, WL_WARNING);
2284 if (bad_font && base_font) {
2285 /* Our fallback font does miss characters too, so keep the
2286 * user chosen font as that is more likely to be any good than
2287 * the wild guess we made */
2288 InitFontCache(searcher->Monospace());
2291 #endif
2293 if (bad_font) {
2294 /* All attempts have failed. Display an error. As we do not want the string to be translated by
2295 * the translators, we 'force' it into the binary and 'load' it via a BindCString. To do this
2296 * properly we have to set the colour of the string, otherwise we end up with a lot of artifacts.
2297 * The colour 'character' might change in the future, so for safety we just Utf8 Encode it into
2298 * the string, which takes exactly three characters, so it replaces the "XXX" with the colour marker. */
2299 static std::string err_str("XXXThe current font is missing some of the characters used in the texts for this language. Read the readme to see how to solve this.");
2300 Utf8Encode(err_str.data(), SCC_YELLOW);
2301 SetDParamStr(0, err_str);
2302 ShowErrorMessage(STR_JUST_RAW_STRING, INVALID_STRING_ID, WL_WARNING);
2304 /* Reset the font width */
2305 LoadStringWidthTable(searcher->Monospace());
2306 return;
2309 /* Update the font with cache */
2310 LoadStringWidthTable(searcher->Monospace());
2312 #if !(defined(WITH_ICU_I18N) && defined(WITH_HARFBUZZ)) && !defined(WITH_UNISCRIBE) && !defined(WITH_COCOA)
2314 * For right-to-left languages we need the ICU library. If
2315 * we do not have support for that library we warn the user
2316 * about it with a message. As we do not want the string to
2317 * be translated by the translators, we 'force' it into the
2318 * binary and 'load' it via a BindCString. To do this
2319 * properly we have to set the colour of the string,
2320 * otherwise we end up with a lot of artifacts. The colour
2321 * 'character' might change in the future, so for safety
2322 * we just Utf8 Encode it into the string, which takes
2323 * exactly three characters, so it replaces the "XXX" with
2324 * the colour marker.
2326 if (_current_text_dir != TD_LTR) {
2327 static std::string err_str("XXXThis version of OpenTTD does not support right-to-left languages. Recompile with ICU + Harfbuzz enabled.");
2328 Utf8Encode(err_str.data(), SCC_YELLOW);
2329 SetDParamStr(0, err_str);
2330 ShowErrorMessage(STR_JUST_RAW_STRING, INVALID_STRING_ID, WL_ERROR);
2332 #endif /* !(WITH_ICU_I18N && WITH_HARFBUZZ) && !WITH_UNISCRIBE && !WITH_COCOA */