44 #include "table/strings.h"
46 #include "3rdparty/fmt/std.h"
48 #include "strings_internal.h"
70 for (
auto ¶m : this->
parameters) param.type = 0;
85 throw std::out_of_range(
"Trying to read invalid string parameter");
89 if (param.type != 0 && param.type != this->next_type) {
91 throw std::out_of_range(
"Trying to read string parameter with wrong type");
106 _global_string_params.SetParam(n, v);
116 return std::get<uint64_t>(_global_string_params.GetParam(n));
130 while (max_value >= 10) {
148 uint64_t val = count > 1 ? front : next;
149 for (; count > 1; count--) {
150 val = 10 * val + next;
161 for (
size_t i = 0; i < backup.size(); i++) {
162 _global_string_params.SetParam(i, backup[i]);
174 for (
size_t i = 0; i < backup.size(); i++) {
175 backup[i] = _global_string_params.GetParam(i);
186 for (
size_t i = 0; i < backup.size(); i++) {
187 if (backup[i] != _global_string_params.GetParam(i))
return true;
193 static void GetSpecialTownNameString(
StringBuilder &builder,
int ind, uint32_t seed);
206 delete[]
reinterpret_cast<char*
>(langpack);
211 std::unique_ptr<LanguagePack, LanguagePackDeleter> langpack;
213 std::vector<char *> offsets;
224 const char *GetStringPtr(
StringID string)
229 case TEXT_TAB_OLD_NEWGRF: NOT_REACHED();
255 if (index >= 0xC0 && !game_script) {
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)";
266 case TEXT_TAB_SPECIAL:
267 if (index >= 0xE4 && !game_script) {
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)";
278 case TEXT_TAB_OLD_CUSTOM:
281 FatalError(
"Incorrect conversion of custom name string.");
290 case TEXT_TAB_OLD_NEWGRF:
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);
321 _global_string_params.PrepareForNextRun();
346 _global_string_params.SetParam(n, str);
357 _global_string_params.SetParam(n, str);
369 _global_string_params.SetParam(n, std::move(str));
372 static const char *GetDecimalSeparator()
375 if (
StrEmpty(decimal_separator)) decimal_separator = _langpack.langpack->digit_decimal_separator;
376 return decimal_separator;
387 static const int max_digits = 20;
388 uint64_t divisor = 10000000000000000000ULL;
389 int thousands_offset = (max_digits - 1) % 3;
396 uint64_t num = number;
398 for (
int i = 0; i < max_digits; i++) {
400 if (num >= divisor) {
401 quot = num / divisor;
404 if ((tot |= quot) || i == max_digits - 1) {
405 builder +=
'0' + quot;
406 if ((i % 3) == thousands_offset && i < max_digits - 1) builder += separator;
413 static void FormatCommaNumber(
StringBuilder &builder, int64_t number)
416 if (
StrEmpty(separator)) separator = _langpack.langpack->digit_group_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);
445 const char *
const iec_prefixes[] = {
"",
"Ki",
"Mi",
"Gi",
"Ti",
"Pi",
"Ei"};
447 while (number >= 1024 * 1024) {
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);
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)
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)
480 auto tmp_params = MakeParameters(STR_MONTH_JAN + ymd.month, ymd.year);
481 FormatString(builder, GetStringPtr(STR_FORMAT_DATE_SHORT), tmp_params, case_index);
489 auto tmp_params = MakeParameters(ymd.day, 2, ymd.month + 1, 2, ymd.year);
497 bool negative = number < 0;
499 number *= spec->
rate;
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;
550 builder.Utf8Encode(SCC_POP_COLOUR);
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) {
569 /* Two forms: singular used for one only.
571 * Danish, Dutch, English, German, Norwegian, Swedish, Estonian, Finnish,
572 * Greek, Hebrew, Italian, Portuguese, Spanish, Esperanto */
574 return n != 1 ? 1 : 0;
578 * Hungarian, Japanese, Turkish */
582 /* Two forms: singular used for 0 and 1.
584 * French, Brazilian Portuguese */
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.
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.
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.
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.
609 * Croatian, Russian, Ukrainian */
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.
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.
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.
629 return n % 10 == 1 && n % 100 != 11 ? 0 : 1;
631 /* Three forms: special cases for 1, and 2 to 4
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.
666 return (n == 1 ? 0 : n == 0 || (n % 100 > 1 && n % 100 < 11) ? 1 : (n % 100 > 10 && n % 100 < 20) ? 2 : 3);
671 return ((n == 1 || n == 11) ? 0 : (n == 2 || n == 12) ? 1 : ((n > 2 && n < 11) || (n > 12 && n < 20)) ? 2 : 3);
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)
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;
693 builder += b + mypos;
707 int64_t
ToDisplay(int64_t input,
bool round =
true)
const
710 ? (int64_t)std::round(input * this->factor)
711 : (int64_t)(input * this->factor);
721 int64_t
FromDisplay(int64_t input,
bool round =
true, int64_t divider = 1)
const
724 ? (int64_t)std::round(input / this->factor / divider)
725 : (int64_t)(input / this->factor / divider);
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 },
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 },
764 { { 1.0 }, STR_UNITS_POWER_IMPERIAL, 0 },
765 { { 1.01387 }, STR_UNITS_POWER_METRIC, 0 },
766 { { 0.745699 }, STR_UNITS_POWER_SI, 0 },
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 },
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 },
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 },
798 { { 0.224809 }, STR_UNITS_FORCE_IMPERIAL, 0 },
799 { { 0.101972 }, STR_UNITS_FORCE_METRIC, 0 },
800 { { 0.001 }, STR_UNITS_FORCE_SI, 0 },
805 { { 3.0 }, STR_UNITS_HEIGHT_IMPERIAL, 0 },
806 { { 1.0 }, STR_UNITS_HEIGHT_METRIC, 0 },
807 { { 1.0 }, STR_UNITS_HEIGHT_SI, 0 },
812 { { 1 }, STR_UNITS_DAYS, 0 },
813 { { 2 }, STR_UNITS_SECONDS, 0 },
818 { { 1 }, STR_UNITS_MONTHS, 0 },
819 { { 1 }, STR_UNITS_MINUTES, 0 },
824 { { 1 }, STR_UNITS_YEARS, 0 },
825 { { 1 }, STR_UNITS_PERIODS, 0 },
830 { { 1 }, STR_UNITS_YEARS, 0 },
831 { { 12 }, STR_UNITS_MINUTES, 0 },
924 FormatString(dry_run_builder, str_arg, args, case_index, game_script,
true);
927 FormatString(dry_run_builder, str_arg, args, case_index, game_script,
true);
933 uint next_substr_case_index = 0;
934 std::stack<const char *, std::vector<const char *>> str_stack;
935 str_stack.push(str_arg);
939 while (!str_stack.empty() && (b = Utf8Consume(&str_stack.top())) ==
'\0') {
942 if (str_stack.empty())
break;
943 const char *&str = str_stack.top();
949 if (b == 0)
continue;
952 if (b < SCC_CONTROL_START || b > SCC_CONTROL_END) {
957 args.SetTypeOfNextParameter(b);
963 uint32_t stringid = std::strtoul(str, &p, 16);
964 if (*p !=
':' && *p !=
'\0') {
965 while (*p !=
'\0') p++;
967 builder +=
"(invalid SCC_ENCODED)";
971 while (*p !=
'\0') p++;
973 builder +=
"(invalid StringID)";
978 while (*p !=
'\0' && i < 20) {
983 bool instring =
false;
990 if (*p ==
'"' && escape) {
997 instring = !instring;
1004 if (*p ==
':')
break;
1005 if (*p ==
'\0')
break;
1012 bool lookup = (l == SCC_ENCODED);
1013 if (lookup) s += len;
1015 param = std::strtoull(s, &p, 16);
1019 while (*p !=
'\0') p++;
1021 builder +=
"(invalid sub-StringID)";
1027 sub_args.SetParam(i++, param);
1030 sub_args.SetParam(i++, std::string(s, p - s - 1));
1042 StringID substr = Utf8Consume(&str);
1043 str_stack.push(GetStringPtr(substr));
1049 str_stack.push(GetStringPtr(substr));
1050 case_index = next_substr_case_index;
1051 next_substr_case_index = 0;
1056 case SCC_GENDER_LIST: {
1058 size_t offset = orig_offset + (uint8_t)*str++;
1078 const char *s = buffer.c_str();
1079 char32_t c = Utf8Consume(&s);
1081 if (c == SCC_GENDER_INDEX) gender = (uint8_t)s[0];
1083 str = ParseStringChoice(str, gender, builder);
1089 case SCC_GENDER_INDEX:
1098 case SCC_PLURAL_LIST: {
1099 int plural_form = *str++;
1100 size_t offset = orig_offset + (uint8_t)*str++;
1101 int64_t v = std::get<uint64_t>(args.GetParam(offset));
1106 case SCC_ARG_INDEX: {
1107 args.
SetOffset(orig_offset + (uint8_t)*str++);
1111 case SCC_SET_CASE: {
1114 next_substr_case_index = (uint8_t)*str++;
1118 case SCC_SWITCH_CASE: {
1121 uint num = (uint8_t)*str++;
1123 if ((uint8_t)str[0] == case_index) {
1129 str += 3 + (str[1] << 8) + str[2];
1136 builder += _openttd_revision;
1139 case SCC_RAW_STRING_POINTER: {
1142 if (raw_string ==
nullptr) {
1143 builder +=
"(invalid RAW_STRING parameter)";
1155 GetStringWithArgs(builder, string_id, tmp_params, next_substr_case_index, game_script);
1156 next_substr_case_index = 0;
1170 uint size = b - SCC_STRING1 + 1;
1172 builder +=
"(too many parameters)";
1175 GetStringWithArgs(builder, string_id, sub_args, next_substr_case_index, game_script);
1178 next_substr_case_index = 0;
1190 FormatCommaNumber(builder, number);
1195 int64_t fractional = number % divisor;
1197 FormatCommaNumber(builder, number);
1198 fmt::format_to(builder,
"{}{:0{}d}", GetDecimalSeparator(), fractional, digits);
1206 case SCC_ZEROFILL_NUM: {
1220 case SCC_CARGO_TINY: {
1229 switch (cargo_str) {
1244 FormatCommaNumber(builder, amount);
1248 case SCC_CARGO_SHORT: {
1256 switch (cargo_str) {
1260 auto tmp_params = MakeParameters(x.c.ToDisplay(args.
GetNextParameter<int64_t>()), x.decimal_places);
1268 auto tmp_params = MakeParameters(x.c.ToDisplay(args.
GetNextParameter<int64_t>()), x.decimal_places);
1282 case SCC_CARGO_LONG: {
1293 case SCC_CARGO_LIST: {
1298 if (!
HasBit(cmask, cs->Index()))
continue;
1307 GetStringWithArgs(builder, cs->name, args, next_substr_case_index, game_script);
1311 if (first)
GetStringWithArgs(builder, STR_JUST_NOTHING, args, next_substr_case_index, game_script);
1313 next_substr_case_index = 0;
1317 case SCC_CURRENCY_SHORT:
1321 case SCC_CURRENCY_LONG:
1326 FormatTinyOrISODate(builder, args.
GetNextParameter<TimerGameCalendar::Date>(), STR_FORMAT_DATE_TINY);
1329 case SCC_DATE_SHORT:
1330 FormatMonthAndYear(builder, args.
GetNextParameter<TimerGameCalendar::Date>(), next_substr_case_index);
1331 next_substr_case_index = 0;
1335 FormatYmdString(builder, args.
GetNextParameter<TimerGameCalendar::Date>(), next_substr_case_index);
1336 next_substr_case_index = 0;
1340 FormatTinyOrISODate(builder, args.
GetNextParameter<TimerGameCalendar::Date>(), STR_FORMAT_DATE_ISO);
1346 auto tmp_params = MakeParameters(x.c.ToDisplay(args.
GetNextParameter<int64_t>()), x.decimal_places);
1354 auto tmp_params = MakeParameters(x.c.ToDisplay(args.
GetNextParameter<int64_t>()), x.decimal_places);
1362 auto tmp_params = MakeParameters(x.c.ToDisplay(args.
GetNextParameter<int64_t>()), x.decimal_places);
1367 case SCC_POWER_TO_WEIGHT: {
1371 auto tmp_params = MakeParameters(x.c.ToDisplay(args.
GetNextParameter<int64_t>()), x.decimal_places);
1376 case SCC_VELOCITY: {
1386 case SCC_VOLUME_SHORT: {
1389 auto tmp_params = MakeParameters(x.c.ToDisplay(args.
GetNextParameter<int64_t>()), x.decimal_places);
1394 case SCC_VOLUME_LONG: {
1397 auto tmp_params = MakeParameters(x.c.ToDisplay(args.
GetNextParameter<int64_t>()), x.decimal_places);
1402 case SCC_WEIGHT_SHORT: {
1405 auto tmp_params = MakeParameters(x.c.ToDisplay(args.
GetNextParameter<int64_t>()), x.decimal_places);
1410 case SCC_WEIGHT_LONG: {
1413 auto tmp_params = MakeParameters(x.c.ToDisplay(args.
GetNextParameter<int64_t>()), x.decimal_places);
1418 case SCC_UNITS_DAYS_OR_SECONDS: {
1421 auto tmp_params = MakeParameters(x.c.ToDisplay(args.
GetNextParameter<int64_t>()), x.decimal_places);
1426 case SCC_UNITS_MONTHS_OR_MINUTES: {
1429 auto tmp_params = MakeParameters(x.c.ToDisplay(args.
GetNextParameter<int64_t>()), x.decimal_places);
1434 case SCC_UNITS_YEARS_OR_PERIODS: {
1437 auto tmp_params = MakeParameters(x.c.ToDisplay(args.
GetNextParameter<int64_t>()), x.decimal_places);
1442 case SCC_UNITS_YEARS_OR_MINUTES: {
1445 auto tmp_params = MakeParameters(x.c.ToDisplay(args.
GetNextParameter<int64_t>()), x.decimal_places);
1450 case SCC_COMPANY_NAME: {
1452 if (c ==
nullptr)
break;
1454 if (!c->
name.empty()) {
1455 auto tmp_params = MakeParameters(c->
name);
1458 auto tmp_params = MakeParameters(c->
name_2);
1464 case SCC_COMPANY_NUM: {
1469 auto tmp_params = MakeParameters(company + 1);
1475 case SCC_DEPOT_NAME: {
1484 if (!d->name.empty()) {
1485 auto tmp_params = MakeParameters(d->name);
1488 auto tmp_params = MakeParameters(d->town->
index, d->
town_cn + 1);
1494 case SCC_ENGINE_NAME: {
1497 if (e ==
nullptr)
break;
1499 if (!e->name.empty() && e->IsEnabled()) {
1500 auto tmp_params = MakeParameters(e->name);
1509 const GRFFile *grffile = e->GetGRF();
1510 assert(grffile !=
nullptr);
1526 case SCC_GROUP_NAME: {
1528 if (g ==
nullptr)
break;
1530 if (!g->
name.empty()) {
1531 auto tmp_params = MakeParameters(g->
name);
1534 auto tmp_params = MakeParameters(g->
number);
1540 case SCC_INDUSTRY_NAME: {
1542 if (i ==
nullptr)
break;
1544 static bool use_cache =
true;
1550 }
else if (use_cache) {
1552 builder += i->GetCachedName();
1556 FormatString(builder, GetStringPtr(STR_FORMAT_INDUSTRY_NAME), tmp_params, next_substr_case_index);
1558 next_substr_case_index = 0;
1562 case SCC_PRESIDENT_NAME: {
1564 if (c ==
nullptr)
break;
1576 case SCC_STATION_NAME: {
1580 if (st ==
nullptr) {
1589 static bool use_cache =
true;
1592 builder += st->GetCachedName();
1593 }
else if (!st->name.empty()) {
1594 auto tmp_params = MakeParameters(st->name);
1597 StringID string_id = st->string_id;
1598 if (st->indtype != IT_INVALID) {
1610 auto tmp_params = MakeParameters(STR_TOWN_NAME, st->town->index, st->index);
1616 case SCC_TOWN_NAME: {
1618 if (t ==
nullptr)
break;
1620 static bool use_cache =
true;
1623 builder += t->GetCachedName();
1624 }
else if (!t->
name.empty()) {
1625 auto tmp_params = MakeParameters(t->
name);
1633 case SCC_WAYPOINT_NAME: {
1635 if (wp ==
nullptr)
break;
1637 if (!wp->
name.empty()) {
1638 auto tmp_params = MakeParameters(wp->
name);
1642 StringID string_id = ((wp->
string_id == STR_SV_STNAME_BUOY) ? STR_FORMAT_BUOY_NAME : STR_FORMAT_WAYPOINT_NAME);
1643 if (wp->
town_cn != 0) string_id++;
1649 case SCC_VEHICLE_NAME: {
1651 if (v ==
nullptr)
break;
1653 if (!v->
name.empty()) {
1654 auto tmp_params = MakeParameters(v->
name);
1661 auto tmp_params = MakeParameters(v->
unitnumber);
1665 default: string_id = STR_INVALID_VEHICLE;
break;
1666 case VEH_TRAIN: string_id = STR_SV_TRAIN_NAME;
break;
1667 case VEH_ROAD: string_id = STR_SV_ROAD_VEHICLE_NAME;
break;
1668 case VEH_SHIP: string_id = STR_SV_SHIP_NAME;
break;
1669 case VEH_AIRCRAFT: string_id = STR_SV_AIRCRAFT_NAME;
break;
1677 case SCC_SIGN_NAME: {
1679 if (si ==
nullptr)
break;
1681 if (!si->name.empty()) {
1682 auto tmp_params = MakeParameters(si->name);
1691 case SCC_STATION_FEATURES: {
1706 }
catch (std::out_of_range &e) {
1707 Debug(misc, 0,
"FormatString: {}", e.what());
1708 builder +=
"(invalid parameter)";
1723 static void GetSpecialTownNameString(
StringBuilder &builder,
int ind, uint32_t seed)
1728 static const char *
const _silly_company_names[] = {
1730 "Tiny Transport Ltd.",
1732 "Comfy-Coach & Co.",
1733 "Crush & Bump Ltd.",
1734 "Broken & Late Ltd.",
1736 "Supersonic Travel",
1738 "Lightning International",
1739 "Pannik & Loozit Ltd.",
1740 "Inter-City Transport",
1741 "Getout & Pushit Ltd."
1744 static const char *
const _surname_list[] = {
1776 static const char *
const _silly_surname_list[] = {
1791 static const char _initial_name_letters[] = {
1792 'A',
'B',
'C',
'D',
'E',
'F',
'G',
'H',
'I',
'J',
1793 'K',
'L',
'M',
'N',
'P',
'R',
'S',
'T',
'W',
1796 static std::span<const char * const> GetSurnameOptions()
1799 return _surname_list;
1809 auto surname_options = GetSurnameOptions();
1810 return surname_options[surname_options.size() *
GB(seed, 16, 8) >> 8];
1813 static void GenAndCoName(
StringBuilder &builder, uint32_t seed)
1816 builder +=
" & Co.";
1819 static void GenPresidentName(
StringBuilder &builder, uint32_t seed)
1821 builder += _initial_name_letters[std::size(_initial_name_letters) *
GB(seed, 0, 8) >> 8];
1825 size_t index = (std::size(_initial_name_letters) + 35) *
GB(seed, 8, 8) >> 8;
1826 if (index < std::size(_initial_name_letters)) {
1827 builder += _initial_name_letters[index];
1838 builder += _silly_company_names[std::min<size_t>(args.
GetNextParameter<uint16_t>(), std::size(_silly_company_names) - 1)];
1851 if (
IsInsideMM(ind - 6, 0, SPECSTR_TOWNNAME_LAST - SPECSTR_TOWNNAME_START + 1)) {
1852 GetSpecialTownNameString(builder, ind - 6, args.
GetNextParameter<uint32_t>());
1853 builder +=
" Transport";
1867 this->
version == TO_LE32(LANGUAGE_PACK_VERSION) &&
1887 return 4 * this->
missing < LANGUAGE_TOTAL_STRINGS;
1900 if (!lang_pack)
return false;
1903 const char *end = (
char *)lang_pack.get() + len + 1;
1906 if (end <= lang_pack->data || !lang_pack->IsValid()) {
1910 std::array<uint, TEXT_TAB_END> tab_start, tab_num;
1914 uint16_t num = FROM_LE16(lang_pack->offsets[i]);
1917 tab_start[i] = count;
1923 std::vector<char *> offs(count);
1926 char *s = lang_pack->data;
1927 len = (uint8_t)*s++;
1928 for (uint i = 0; i < count; i++) {
1929 if (s + len >= end)
return false;
1932 len = ((len & 0x3F) << 8) + (uint8_t)*s++;
1933 if (s + len >= end)
return false;
1941 _langpack.langpack = std::move(lang_pack);
1942 _langpack.offsets = std::move(offs);
1952 extern void Win32SetCurrentLocaleName(std::string iso_code);
1961 #ifdef WITH_ICU_I18N
1963 UErrorCode status = U_ZERO_ERROR;
1968 if (U_FAILURE(status)) {
1994 #if !(defined(_WIN32) || defined(__APPLE__))
2007 env = std::getenv(
"LANGUAGE");
2008 if (env !=
nullptr)
return env;
2010 env = std::getenv(
"LC_ALL");
2011 if (env !=
nullptr)
return env;
2013 if (param !=
nullptr) {
2014 env = std::getenv(param);
2015 if (env !=
nullptr)
return env;
2018 return std::getenv(
"LANG");
2032 if (newgrflangid == lang.newgrflangid)
return ⟨
2046 FILE *f = fopen(file.c_str(),
"rb");
2047 if (f ==
nullptr)
return false;
2049 size_t read = fread(hdr,
sizeof(*hdr), 1, f);
2052 bool ret = read == 1 && hdr->
IsValid();
2068 std::error_code error_code;
2069 for (
const auto &dir_entry : std::filesystem::directory_iterator(
OTTD2FS(path), error_code)) {
2070 if (!dir_entry.is_regular_file())
continue;
2071 if (dir_entry.path().extension() !=
".lng")
continue;
2074 lmd.
file = dir_entry.path();
2086 Debug(misc, 9,
"Unable to open directory {}: {}", path, error_code.message());
2099 if (
_languages.empty()) UserError(
"No available language packs (invalid versions?)");
2103 if (lang ==
nullptr) lang =
"en_GB";
2115 chosen_language = &lng;
2119 if (strcmp (lng.isocode,
"en_GB") == 0) en_GB_fallback = &lng;
2122 if (!lng.IsReasonablyFinished())
continue;
2124 if (strncmp(lng.isocode, lang, 5) == 0) chosen_language = &lng;
2125 if (strncmp(lng.isocode, lang, 2) == 0) language_fallback = &lng;
2130 if (chosen_language ==
nullptr) {
2131 chosen_language = (language_fallback !=
nullptr) ? language_fallback : en_GB_fallback;
2143 return _langpack.langpack->isocode;
2156 auto src = text->cbegin();
2160 while (src != text->cend()) {
2161 char32_t c = Utf8Consume(src);
2163 if (c >= SCC_FIRST_FONT && c <= SCC_LAST_FONT) {
2164 size = (
FontSize)(c - SCC_FIRST_FONT);
2168 std::string size_name;
2171 case FS_NORMAL: size_name =
"medium";
break;
2172 case FS_SMALL: size_name =
"small";
break;
2173 case FS_LARGE: size_name =
"large";
break;
2174 case FS_MONO: size_name =
"mono";
break;
2175 default: NOT_REACHED();
2178 Debug(fontcache, 0,
"Font is missing glyphs to display char 0x{:X} in {} font size", (
int)c, size_name);
2206 const char *ret = _langpack.offsets[_langpack.
langtab_start[this->
i] + this->
j];
2209 while (this->i < TEXT_TAB_END && this->
j >= _langpack.
langtab_num[this->i]) {
2222 void SetFontNames([[maybe_unused]]
FontCacheSettings *
settings, [[maybe_unused]]
const char *font_name, [[maybe_unused]]
const void *os_data)
override
2224 #if defined(WITH_FREETYPE) || defined(_WIN32) || defined(WITH_COCOA)
2229 settings->small.os_handle = os_data;
2230 settings->medium.os_handle = os_data;
2231 settings->large.os_handle = os_data;
2252 if (searcher ==
nullptr) searcher = &pack_searcher;
2254 #if defined(WITH_FREETYPE) || defined(_WIN32) || defined(WITH_COCOA)
2258 bool any_font_configured = !_fcsettings.
medium.
font.empty();
2264 bad_font = !
SetFallbackFont(&_fcsettings, _langpack.langpack->isocode, _langpack.langpack->winlangid, searcher);
2266 _fcsettings = backup;
2268 if (!bad_font && any_font_configured) {
2275 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.");
2281 if (bad_font && base_font) {
2296 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.");
2309 #if !(defined(WITH_ICU_I18N) && defined(WITH_HARFBUZZ)) && !defined(WITH_UNISCRIBE) && !defined(WITH_COCOA)
2324 static std::string err_str(
"XXXThis version of OpenTTD does not support right-to-left languages. Recompile with ICU + Harfbuzz enabled.");