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 _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 auto &value = backup[i];
163 if (value.string.has_value()) {
164 _global_string_params.SetParam(i, value.string.value());
166 _global_string_params.SetParam(i, value.data);
179 for (
size_t i = 0; i < backup.size(); i++) {
180 const char *str = _global_string_params.GetParamStr(i);
181 if (str !=
nullptr) {
184 backup[i] = _global_string_params.GetParam(i);
196 bool changed =
false;
197 for (
size_t i = 0; !changed && i < backup.size(); i++) {
198 bool global_has_string = _global_string_params.GetParamStr(i) !=
nullptr;
199 if (global_has_string != backup[i].
string.has_value())
return true;
201 if (global_has_string) {
202 changed = backup[i].string.value() != _global_string_params.GetParamStr(i);
204 changed = backup[i].data != _global_string_params.GetParam(i);
211 static void GetSpecialTownNameString(
StringBuilder &builder,
int ind, uint32_t seed);
224 delete[]
reinterpret_cast<char*
>(langpack);
229 std::unique_ptr<LanguagePack, LanguagePackDeleter> langpack;
231 std::vector<char *> offsets;
242 const char *GetStringPtr(
StringID string)
247 case TEXT_TAB_OLD_NEWGRF: NOT_REACHED();
273 if (index >= 0xC0 && !game_script) {
274 GetSpecialTownNameString(builder, index - 0xC0, args.
GetNextParameter<uint32_t>());
279 case TEXT_TAB_SPECIAL:
280 if (index >= 0xE4 && !game_script) {
281 GetSpecialNameString(builder, index - 0xE4, args);
286 case TEXT_TAB_OLD_CUSTOM:
289 FatalError(
"Incorrect conversion of custom name string.");
298 case TEXT_TAB_OLD_NEWGRF:
314 FatalError(
"String 0x{:X} is invalid. You are probably using an old version of the .lng file.\n",
string);
317 FormatString(builder, GetStringPtr(
string), args, case_index);
329 _global_string_params.PrepareForNextRun();
354 _global_string_params.SetParam(n, str);
365 _global_string_params.SetParam(n, str);
377 _global_string_params.SetParam(n, std::move(str));
392 static const int max_digits = 20;
393 uint64_t divisor = 10000000000000000000ULL;
394 zerofill += fractional_digits;
395 int thousands_offset = (max_digits - fractional_digits - 1) % 3;
402 uint64_t num = number;
404 for (
int i = 0; i < max_digits; i++) {
405 if (i == max_digits - fractional_digits) {
407 if (
StrEmpty(decimal_separator)) decimal_separator = _langpack.langpack->digit_decimal_separator;
408 builder += decimal_separator;
412 if (num >= divisor) {
413 quot = num / divisor;
416 if ((tot |= quot) || i >= max_digits - zerofill) {
417 builder +=
'0' + quot;
418 if ((i % 3) == thousands_offset && i < max_digits - 1 - fractional_digits) builder += separator;
425 static void FormatCommaNumber(
StringBuilder &builder, int64_t number,
int fractional_digits = 0)
428 if (
StrEmpty(separator)) separator = _langpack.langpack->digit_group_separator;
429 FormatNumber(builder, number, separator, 1, fractional_digits);
432 static void FormatNoCommaNumber(
StringBuilder &builder, int64_t number)
437 static void FormatZerofillNumber(
StringBuilder &builder, int64_t number,
int count)
442 static void FormatHexNumber(
StringBuilder &builder, uint64_t number)
444 fmt::format_to(builder,
"0x{:X}", number);
457 const char *
const iec_prefixes[] = {
"",
"Ki",
"Mi",
"Gi",
"Ti",
"Pi",
"Ei"};
459 while (number >= 1024 * 1024) {
465 if (
StrEmpty(decimal_separator)) decimal_separator = _langpack.langpack->digit_decimal_separator;
469 fmt::format_to(builder,
"{}", number);
470 }
else if (number < 1024 * 10) {
471 fmt::format_to(builder,
"{}{}{:02}", number / 1024, decimal_separator, (number % 1024) * 100 / 1024);
472 }
else if (number < 1024 * 100) {
473 fmt::format_to(builder,
"{}{}{:01}", number / 1024, decimal_separator, (number % 1024) * 10 / 1024);
475 assert(number < 1024 * 1024);
476 fmt::format_to(builder,
"{}", number / 1024);
479 assert(
id <
lengthof(iec_prefixes));
480 fmt::format_to(builder,
NBSP "{}B", iec_prefixes[
id]);
483 static void FormatYmdString(
StringBuilder &builder, TimerGameCalendar::Date date, uint case_index)
487 auto tmp_params = MakeParameters(ymd.day + STR_DAY_NUMBER_1ST - 1, STR_MONTH_ABBREV_JAN + ymd.month, ymd.year);
488 FormatString(builder, GetStringPtr(STR_FORMAT_DATE_LONG), tmp_params, case_index);
491 static void FormatMonthAndYear(
StringBuilder &builder, TimerGameCalendar::Date date, uint case_index)
495 auto tmp_params = MakeParameters(STR_MONTH_JAN + ymd.month, ymd.year);
496 FormatString(builder, GetStringPtr(STR_FORMAT_DATE_SHORT), tmp_params, case_index);
504 auto tmp_params = MakeParameters(ymd.day, 2, ymd.month + 1, 2, ymd.year);
512 bool negative = number < 0;
514 number *= spec->
rate;
535 if (number >=
Money(1
'000'000
'000'000
'000) - 500'000
'000) {
536 number = (number + Money(500'000
'000'000)) /
Money(1
'000'000
'000'000);
537 number_str = STR_CURRENCY_SHORT_TERA;
538 }
else if (number >=
Money(1
'000'000
'000'000) - 500
'000) {
539 number = (number + 500'000
'000) / 1'000
'000'000;
540 number_str = STR_CURRENCY_SHORT_GIGA;
541 }
else if (number >= 1
'000'000
'000 - 500) {
542 number = (number + 500'000) / 1
'000'000;
543 number_str = STR_CURRENCY_SHORT_MEGA;
544 }
else if (number >= 1
'000'000) {
545 number = (number + 500) / 1
'000;
546 number_str = STR_CURRENCY_SHORT_KILO;
550 const char *separator = _settings_game.locale.digit_group_separator_currency.c_str();
551 if (StrEmpty(separator)) separator = _currency->separator.c_str();
552 if (StrEmpty(separator)) separator = _langpack.langpack->digit_group_separator_currency;
553 FormatNumber(builder, number, separator);
554 if (number_str != STR_NULL) {
555 auto tmp_params = ArrayStringParameters<0>();
556 FormatString(builder, GetStringPtr(number_str), tmp_params);
559 /* Add suffix part, following symbol_pos specification.
560 * Here, it can can be either 1 (suffix) or 2 (both prefix and suffix).
561 * The only remaining value is 1 (prefix), so everything that is not 0 */
562 if (spec->symbol_pos != 0) builder += spec->suffix;
565 builder.Utf8Encode(SCC_POP_COLOUR);
575 static int DeterminePluralForm(int64_t count, int plural_form)
577 /* The absolute value determines plurality */
578 uint64_t n = abs(count);
580 switch (plural_form) {
584 /* Two forms: singular used for one only.
586 * Danish, Dutch, English, German, Norwegian, Swedish, Estonian, Finnish,
587 * Greek, Hebrew, Italian, Portuguese, Spanish, Esperanto */
589 return n != 1 ? 1 : 0;
593 * Hungarian, Japanese, Turkish */
597 /* Two forms: singular used for 0 and 1.
599 * French, Brazilian Portuguese */
601 return n > 1 ? 1 : 0;
603 /* Three forms: special cases for 0, and numbers ending in 1 except when ending in 11.
604 * Note: Cases are out of order for hysterical reasons. '0
' is last.
608 return n % 10 == 1 && n % 100 != 11 ? 0 : n != 0 ? 1 : 2;
610 /* Five forms: special cases for 1, 2, 3 to 6, and 7 to 10.
614 return n == 1 ? 0 : n == 2 ? 1 : n < 7 ? 2 : n < 11 ? 3 : 4;
616 /* 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.
620 return n % 10 == 1 && n % 100 != 11 ? 0 : n % 10 >= 2 && (n % 100 < 10 || n % 100 >= 20) ? 1 : 2;
622 /* 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.
624 * Croatian, Russian, Ukrainian */
626 return n % 10 == 1 && n % 100 != 11 ? 0 : n % 10 >= 2 && n % 10 <= 4 && (n % 100 < 10 || n % 100 >= 20) ? 1 : 2;
628 /* Three forms: special cases for 1, and numbers ending in 2 to 4 except when ending in 12 to 14.
632 return n == 1 ? 0 : n % 10 >= 2 && n % 10 <= 4 && (n % 100 < 10 || n % 100 >= 20) ? 1 : 2;
634 /* Four forms: special cases for numbers ending in 01, 02, and 03 to 04.
638 return n % 100 == 1 ? 0 : n % 100 == 2 ? 1 : n % 100 == 3 || n % 100 == 4 ? 2 : 3;
640 /* Two forms: singular used for numbers ending in 1 except when ending in 11.
644 return n % 10 == 1 && n % 100 != 11 ? 0 : 1;
646 /* Three forms: special cases for 1, and 2 to 4
650 return n == 1 ? 0 : n >= 2 && n <= 4 ? 1 : 2;
652 /* Two forms: cases for numbers ending with a consonant, and with a vowel.
653 * Korean doesn't have the concept of plural, but depending on how a
654 * number is pronounced it needs another version of a particle.
655 * As such the plural system is misused to give
this distinction.
681 return (n == 1 ? 0 : n == 0 || (n % 100 > 1 && n % 100 < 11) ? 1 : (n % 100 > 10 && n % 100 < 20) ? 2 : 3);
686 return ((n == 1 || n == 11) ? 0 : (n == 2 || n == 12) ? 1 : ((n > 2 && n < 11) || (n > 12 && n < 20)) ? 2 : 3);
692 return n == 1 ? 0 : (n == 0 || (n % 100 > 0 && n % 100 < 20)) ? 1 : 2;
696 static const char *ParseStringChoice(
const char *b, uint form,
StringBuilder &builder)
700 uint pos, i, mypos = 0;
702 for (i = pos = 0; i != n; i++) {
703 uint len = (byte)*b++;
704 if (i == form) mypos = pos;
708 builder += b + mypos;
722 int64_t
ToDisplay(int64_t input,
bool round =
true)
const
725 ? (int64_t)std::round(input * this->factor)
726 : (int64_t)(input * this->factor);
736 int64_t
FromDisplay(int64_t input,
bool round =
true, int64_t divider = 1)
const
739 ? (int64_t)std::round(input / this->factor / divider)
740 : (int64_t)(input / this->factor / divider);
761 { { 1.0 }, STR_UNITS_VELOCITY_IMPERIAL, 0 },
762 { { 1.609344 }, STR_UNITS_VELOCITY_METRIC, 0 },
763 { { 0.44704 }, STR_UNITS_VELOCITY_SI, 0 },
764 { { 0.578125 }, STR_UNITS_VELOCITY_GAMEUNITS_DAY, 1 },
765 { { 0.868976 }, STR_UNITS_VELOCITY_KNOTS, 0 },
770 { { 1.0 }, STR_UNITS_VELOCITY_IMPERIAL, 0 },
771 { { 1.609344 }, STR_UNITS_VELOCITY_METRIC, 0 },
772 { { 0.44704 }, STR_UNITS_VELOCITY_SI, 0 },
773 { { 0.289352 }, STR_UNITS_VELOCITY_GAMEUNITS_SEC, 1 },
774 { { 0.868976 }, STR_UNITS_VELOCITY_KNOTS, 0 },
779 { { 1.0 }, STR_UNITS_POWER_IMPERIAL, 0 },
780 { { 1.01387 }, STR_UNITS_POWER_METRIC, 0 },
781 { { 0.745699 }, STR_UNITS_POWER_SI, 0 },
786 { { 0.907185 }, STR_UNITS_POWER_IMPERIAL_TO_WEIGHT_IMPERIAL, 1 },
787 { { 1.0 }, STR_UNITS_POWER_IMPERIAL_TO_WEIGHT_METRIC, 1 },
788 { { 1.0 }, STR_UNITS_POWER_IMPERIAL_TO_WEIGHT_SI, 1 },
789 { { 0.919768 }, STR_UNITS_POWER_METRIC_TO_WEIGHT_IMPERIAL, 1 },
790 { { 1.01387 }, STR_UNITS_POWER_METRIC_TO_WEIGHT_METRIC, 1 },
791 { { 1.01387 }, STR_UNITS_POWER_METRIC_TO_WEIGHT_SI, 1 },
792 { { 0.676487 }, STR_UNITS_POWER_SI_TO_WEIGHT_IMPERIAL, 1 },
793 { { 0.745699 }, STR_UNITS_POWER_SI_TO_WEIGHT_METRIC, 1 },
794 { { 0.745699 }, STR_UNITS_POWER_SI_TO_WEIGHT_SI, 1 },
799 { { 1.102311 }, STR_UNITS_WEIGHT_SHORT_IMPERIAL, STR_UNITS_WEIGHT_LONG_IMPERIAL, 0 },
800 { { 1.0 }, STR_UNITS_WEIGHT_SHORT_METRIC, STR_UNITS_WEIGHT_LONG_METRIC, 0 },
801 { { 1000.0 }, STR_UNITS_WEIGHT_SHORT_SI, STR_UNITS_WEIGHT_LONG_SI, 0 },
806 { { 264.172 }, STR_UNITS_VOLUME_SHORT_IMPERIAL, STR_UNITS_VOLUME_LONG_IMPERIAL, 0 },
807 { { 1000.0 }, STR_UNITS_VOLUME_SHORT_METRIC, STR_UNITS_VOLUME_LONG_METRIC, 0 },
808 { { 1.0 }, STR_UNITS_VOLUME_SHORT_SI, STR_UNITS_VOLUME_LONG_SI, 0 },
813 { { 0.224809 }, STR_UNITS_FORCE_IMPERIAL, 0 },
814 { { 0.101972 }, STR_UNITS_FORCE_METRIC, 0 },
815 { { 0.001 }, STR_UNITS_FORCE_SI, 0 },
820 { { 3.0 }, STR_UNITS_HEIGHT_IMPERIAL, 0 },
821 { { 1.0 }, STR_UNITS_HEIGHT_METRIC, 0 },
822 { { 1.0 }, STR_UNITS_HEIGHT_SI, 0 },
827 { { 1 }, STR_UNITS_DAYS, 0 },
828 { { 2 }, STR_UNITS_SECONDS, 0 },
833 { { 1 }, STR_UNITS_MONTHS, 0 },
834 { { 1 }, STR_UNITS_MINUTES, 0 },
839 { { 1 }, STR_UNITS_YEARS, 0 },
840 { { 1 }, STR_UNITS_PERIODS, 0 },
845 { { 1 }, STR_UNITS_YEARS, 0 },
846 { { 12 }, STR_UNITS_MINUTES, 0 },
939 FormatString(dry_run_builder, str_arg, args, case_index, game_script,
true);
942 FormatString(dry_run_builder, str_arg, args, case_index, game_script,
true);
948 uint next_substr_case_index = 0;
949 std::stack<const char *, std::vector<const char *>> str_stack;
950 str_stack.push(str_arg);
954 while (!str_stack.empty() && (b = Utf8Consume(&str_stack.top())) ==
'\0') {
957 if (str_stack.empty())
break;
958 const char *&str = str_stack.top();
964 if (b == 0)
continue;
967 if (b < SCC_CONTROL_START || b > SCC_CONTROL_END) {
972 args.SetTypeOfNextParameter(b);
978 uint32_t stringid = std::strtoul(str, &p, 16);
979 if (*p !=
':' && *p !=
'\0') {
980 while (*p !=
'\0') p++;
982 builder +=
"(invalid SCC_ENCODED)";
986 while (*p !=
'\0') p++;
988 builder +=
"(invalid StringID)";
993 while (*p !=
'\0' && i < 20) {
998 bool instring =
false;
1005 if (*p ==
'"' && escape) {
1012 instring = !instring;
1019 if (*p ==
':')
break;
1020 if (*p ==
'\0')
break;
1027 bool lookup = (l == SCC_ENCODED);
1028 if (lookup) s += len;
1030 param = std::strtoull(s, &p, 16);
1034 while (*p !=
'\0') p++;
1036 builder +=
"(invalid sub-StringID)";
1042 sub_args.SetParam(i++, param);
1045 sub_args.SetParam(i++, std::string(s, p - s - 1));
1057 StringID substr = Utf8Consume(&str);
1058 str_stack.push(GetStringPtr(substr));
1064 str_stack.push(GetStringPtr(substr));
1065 case_index = next_substr_case_index;
1066 next_substr_case_index = 0;
1071 case SCC_GENDER_LIST: {
1073 size_t offset = orig_offset + (byte)*str++;
1093 const char *s = buffer.c_str();
1094 char32_t c = Utf8Consume(&s);
1096 if (c == SCC_GENDER_INDEX) gender = (byte)s[0];
1098 str = ParseStringChoice(str, gender, builder);
1104 case SCC_GENDER_INDEX:
1113 case SCC_PLURAL_LIST: {
1114 int plural_form = *str++;
1115 size_t offset = orig_offset + (byte)*str++;
1116 int64_t v = args.GetParam(offset);
1121 case SCC_ARG_INDEX: {
1122 args.
SetOffset(orig_offset + (
byte)*str++);
1126 case SCC_SET_CASE: {
1129 next_substr_case_index = (byte)*str++;
1133 case SCC_SWITCH_CASE: {
1136 uint num = (byte)*str++;
1138 if ((
byte)str[0] == case_index) {
1144 str += 3 + (str[1] << 8) + str[2];
1151 builder += _openttd_revision;
1154 case SCC_RAW_STRING_POINTER: {
1157 if (raw_string ==
nullptr) {
1158 builder +=
"(invalid RAW_STRING parameter)";
1170 GetStringWithArgs(builder, string_id, tmp_params, next_substr_case_index, game_script);
1171 next_substr_case_index = 0;
1185 uint size = b - SCC_STRING1 + 1;
1187 builder +=
"(too many parameters)";
1190 GetStringWithArgs(builder, string_id, sub_args, next_substr_case_index, game_script);
1193 next_substr_case_index = 0;
1204 FormatCommaNumber(builder, number, digits);
1212 case SCC_ZEROFILL_NUM: {
1226 case SCC_CARGO_TINY: {
1235 switch (cargo_str) {
1250 FormatCommaNumber(builder, amount);
1254 case SCC_CARGO_SHORT: {
1262 switch (cargo_str) {
1266 auto tmp_params = MakeParameters(x.c.ToDisplay(args.
GetNextParameter<int64_t>()), x.decimal_places);
1274 auto tmp_params = MakeParameters(x.c.ToDisplay(args.
GetNextParameter<int64_t>()), x.decimal_places);
1288 case SCC_CARGO_LONG: {
1299 case SCC_CARGO_LIST: {
1304 if (!
HasBit(cmask, cs->Index()))
continue;
1313 GetStringWithArgs(builder, cs->name, args, next_substr_case_index, game_script);
1317 if (first)
GetStringWithArgs(builder, STR_JUST_NOTHING, args, next_substr_case_index, game_script);
1319 next_substr_case_index = 0;
1323 case SCC_CURRENCY_SHORT:
1324 FormatGenericCurrency(builder, _currency, args.
GetNextParameter<int64_t>(),
true);
1327 case SCC_CURRENCY_LONG:
1328 FormatGenericCurrency(builder, _currency, args.
GetNextParameter<int64_t>(),
false);
1332 FormatTinyOrISODate(builder, args.
GetNextParameter<TimerGameCalendar::Date>(), STR_FORMAT_DATE_TINY);
1335 case SCC_DATE_SHORT:
1336 FormatMonthAndYear(builder, args.
GetNextParameter<TimerGameCalendar::Date>(), next_substr_case_index);
1337 next_substr_case_index = 0;
1341 FormatYmdString(builder, args.
GetNextParameter<TimerGameCalendar::Date>(), next_substr_case_index);
1342 next_substr_case_index = 0;
1346 FormatTinyOrISODate(builder, args.
GetNextParameter<TimerGameCalendar::Date>(), STR_FORMAT_DATE_ISO);
1352 auto tmp_params = MakeParameters(x.c.ToDisplay(args.
GetNextParameter<int64_t>()), x.decimal_places);
1360 auto tmp_params = MakeParameters(x.c.ToDisplay(args.
GetNextParameter<int64_t>()), x.decimal_places);
1368 auto tmp_params = MakeParameters(x.c.ToDisplay(args.
GetNextParameter<int64_t>()), x.decimal_places);
1373 case SCC_POWER_TO_WEIGHT: {
1377 auto tmp_params = MakeParameters(x.c.ToDisplay(args.
GetNextParameter<int64_t>()), x.decimal_places);
1382 case SCC_VELOCITY: {
1392 case SCC_VOLUME_SHORT: {
1395 auto tmp_params = MakeParameters(x.c.ToDisplay(args.
GetNextParameter<int64_t>()), x.decimal_places);
1400 case SCC_VOLUME_LONG: {
1403 auto tmp_params = MakeParameters(x.c.ToDisplay(args.
GetNextParameter<int64_t>()), x.decimal_places);
1408 case SCC_WEIGHT_SHORT: {
1411 auto tmp_params = MakeParameters(x.c.ToDisplay(args.
GetNextParameter<int64_t>()), x.decimal_places);
1416 case SCC_WEIGHT_LONG: {
1419 auto tmp_params = MakeParameters(x.c.ToDisplay(args.
GetNextParameter<int64_t>()), x.decimal_places);
1424 case SCC_UNITS_DAYS_OR_SECONDS: {
1427 auto tmp_params = MakeParameters(x.c.ToDisplay(args.
GetNextParameter<int64_t>()), x.decimal_places);
1432 case SCC_UNITS_MONTHS_OR_MINUTES: {
1435 auto tmp_params = MakeParameters(x.c.ToDisplay(args.
GetNextParameter<int64_t>()), x.decimal_places);
1440 case SCC_UNITS_YEARS_OR_PERIODS: {
1443 auto tmp_params = MakeParameters(x.c.ToDisplay(args.
GetNextParameter<int64_t>()), x.decimal_places);
1448 case SCC_UNITS_YEARS_OR_MINUTES: {
1451 auto tmp_params = MakeParameters(x.c.ToDisplay(args.
GetNextParameter<int64_t>()), x.decimal_places);
1456 case SCC_COMPANY_NAME: {
1458 if (c ==
nullptr)
break;
1460 if (!c->
name.empty()) {
1461 auto tmp_params = MakeParameters(c->
name);
1464 auto tmp_params = MakeParameters(c->
name_2);
1470 case SCC_COMPANY_NUM: {
1475 auto tmp_params = MakeParameters(company + 1);
1481 case SCC_DEPOT_NAME: {
1490 if (!d->name.empty()) {
1491 auto tmp_params = MakeParameters(d->name);
1494 auto tmp_params = MakeParameters(d->town->
index, d->
town_cn + 1);
1500 case SCC_ENGINE_NAME: {
1503 if (e ==
nullptr)
break;
1505 if (!e->name.empty() && e->IsEnabled()) {
1506 auto tmp_params = MakeParameters(e->name);
1515 const GRFFile *grffile = e->GetGRF();
1516 assert(grffile !=
nullptr);
1532 case SCC_GROUP_NAME: {
1534 if (g ==
nullptr)
break;
1536 if (!g->
name.empty()) {
1537 auto tmp_params = MakeParameters(g->
name);
1540 auto tmp_params = MakeParameters(g->
index);
1546 case SCC_INDUSTRY_NAME: {
1548 if (i ==
nullptr)
break;
1550 static bool use_cache =
true;
1556 }
else if (use_cache) {
1558 builder += i->GetCachedName();
1562 FormatString(builder, GetStringPtr(STR_FORMAT_INDUSTRY_NAME), tmp_params, next_substr_case_index);
1564 next_substr_case_index = 0;
1568 case SCC_PRESIDENT_NAME: {
1570 if (c ==
nullptr)
break;
1582 case SCC_STATION_NAME: {
1586 if (st ==
nullptr) {
1595 static bool use_cache =
true;
1598 builder += st->GetCachedName();
1599 }
else if (!st->name.empty()) {
1600 auto tmp_params = MakeParameters(st->name);
1603 StringID string_id = st->string_id;
1604 if (st->indtype != IT_INVALID) {
1616 auto tmp_params = MakeParameters(STR_TOWN_NAME, st->town->index, st->index);
1622 case SCC_TOWN_NAME: {
1624 if (t ==
nullptr)
break;
1626 static bool use_cache =
true;
1629 builder += t->GetCachedName();
1630 }
else if (!t->
name.empty()) {
1631 auto tmp_params = MakeParameters(t->
name);
1639 case SCC_WAYPOINT_NAME: {
1641 if (wp ==
nullptr)
break;
1643 if (!wp->
name.empty()) {
1644 auto tmp_params = MakeParameters(wp->
name);
1648 StringID string_id = ((wp->
string_id == STR_SV_STNAME_BUOY) ? STR_FORMAT_BUOY_NAME : STR_FORMAT_WAYPOINT_NAME);
1649 if (wp->
town_cn != 0) string_id++;
1655 case SCC_VEHICLE_NAME: {
1657 if (v ==
nullptr)
break;
1659 if (!v->
name.empty()) {
1660 auto tmp_params = MakeParameters(v->
name);
1667 auto tmp_params = MakeParameters(v->
unitnumber);
1671 default: string_id = STR_INVALID_VEHICLE;
break;
1672 case VEH_TRAIN: string_id = STR_SV_TRAIN_NAME;
break;
1673 case VEH_ROAD: string_id = STR_SV_ROAD_VEHICLE_NAME;
break;
1674 case VEH_SHIP: string_id = STR_SV_SHIP_NAME;
break;
1675 case VEH_AIRCRAFT: string_id = STR_SV_AIRCRAFT_NAME;
break;
1683 case SCC_SIGN_NAME: {
1685 if (si ==
nullptr)
break;
1687 if (!si->name.empty()) {
1688 auto tmp_params = MakeParameters(si->name);
1697 case SCC_STATION_FEATURES: {
1712 }
catch (std::out_of_range &e) {
1713 Debug(misc, 0,
"FormatString: {}", e.what());
1714 builder +=
"(invalid parameter)";
1729 static void GetSpecialTownNameString(
StringBuilder &builder,
int ind, uint32_t seed)
1734 static const char *
const _silly_company_names[] = {
1736 "Tiny Transport Ltd.",
1738 "Comfy-Coach & Co.",
1739 "Crush & Bump Ltd.",
1740 "Broken & Late Ltd.",
1742 "Supersonic Travel",
1744 "Lightning International",
1745 "Pannik & Loozit Ltd.",
1746 "Inter-City Transport",
1747 "Getout & Pushit Ltd."
1750 static const char *
const _surname_list[] = {
1782 static const char *
const _silly_surname_list[] = {
1797 static const char _initial_name_letters[] = {
1798 'A',
'B',
'C',
'D',
'E',
'F',
'G',
'H',
'I',
'J',
1799 'K',
'L',
'M',
'N',
'P',
'R',
'S',
'T',
'W',
1802 static void GenAndCoName(
StringBuilder &builder, uint32_t arg)
1804 const char *
const *base;
1808 base = _silly_surname_list;
1809 num =
lengthof(_silly_surname_list);
1811 base = _surname_list;
1815 builder += base[num *
GB(arg, 16, 8) >> 8];
1816 builder +=
" & Co.";
1819 static void GenPresidentName(
StringBuilder &builder, uint32_t x)
1821 char initial[] =
"?. ";
1822 const char *
const *base;
1826 initial[0] = _initial_name_letters[
sizeof(_initial_name_letters) *
GB(x, 0, 8) >> 8];
1829 i = (
sizeof(_initial_name_letters) + 35) *
GB(x, 8, 8) >> 8;
1830 if (i <
sizeof(_initial_name_letters)) {
1831 initial[0] = _initial_name_letters[i];
1836 base = _silly_surname_list;
1837 num =
lengthof(_silly_surname_list);
1839 base = _surname_list;
1843 builder += base[num *
GB(x, 16, 8) >> 8];
1850 builder += _silly_company_names[std::min<uint>(args.
GetNextParameter<uint16_t>(),
lengthof(_silly_company_names) - 1)];
1863 if (
IsInsideMM(ind - 6, 0, SPECSTR_TOWNNAME_LAST - SPECSTR_TOWNNAME_START + 1)) {
1864 GetSpecialTownNameString(builder, ind - 6, args.
GetNextParameter<uint32_t>());
1865 builder +=
" Transport";
1879 this->
version == TO_LE32(LANGUAGE_PACK_VERSION) &&
1899 return 4 * this->
missing < LANGUAGE_TOTAL_STRINGS;
1911 std::unique_ptr<LanguagePack, LanguagePackDeleter> lang_pack(
reinterpret_cast<LanguagePack *
>(
ReadFileToMem(lang->
file.string(), len, 1U << 20).release()));
1912 if (!lang_pack)
return false;
1915 const char *end = (
char *)lang_pack.get() + len + 1;
1918 if (end <= lang_pack->data || !lang_pack->IsValid()) {
1922 std::array<uint, TEXT_TAB_END> tab_start, tab_num;
1926 uint16_t num = FROM_LE16(lang_pack->offsets[i]);
1929 tab_start[i] = count;
1935 std::vector<char *> offs(count);
1938 char *s = lang_pack->data;
1940 for (uint i = 0; i < count; i++) {
1941 if (s + len >= end)
return false;
1944 len = ((len & 0x3F) << 8) + (byte)*s++;
1945 if (s + len >= end)
return false;
1953 _langpack.langpack = std::move(lang_pack);
1954 _langpack.offsets = std::move(offs);
1964 extern void Win32SetCurrentLocaleName(std::string iso_code);
1973 #ifdef WITH_ICU_I18N
1975 UErrorCode status = U_ZERO_ERROR;
1980 if (U_FAILURE(status)) {
2006 #if !(defined(_WIN32) || defined(__APPLE__))
2019 env = std::getenv(
"LANGUAGE");
2020 if (env !=
nullptr)
return env;
2022 env = std::getenv(
"LC_ALL");
2023 if (env !=
nullptr)
return env;
2025 if (param !=
nullptr) {
2026 env = std::getenv(param);
2027 if (env !=
nullptr)
return env;
2030 return std::getenv(
"LANG");
2044 if (newgrflangid == lang.newgrflangid)
return ⟨
2058 FILE *f = fopen(file,
"rb");
2059 if (f ==
nullptr)
return false;
2061 size_t read = fread(hdr,
sizeof(*hdr), 1, f);
2064 bool ret = read == 1 && hdr->
IsValid();
2081 if (dir !=
nullptr) {
2082 struct dirent *dirent;
2083 while ((dirent = readdir(dir)) !=
nullptr) {
2084 std::string d_name =
FS2OTTD(dirent->d_name);
2085 const char *extension = strrchr(d_name.c_str(),
'.');
2088 if (extension ==
nullptr || strcmp(extension,
".lng") != 0)
continue;
2091 lmd.
file = path + d_name;
2095 Debug(misc, 3,
"{} is not a valid language file", lmd.
file);
2097 Debug(misc, 3,
"{}'s language ID is already known", lmd.
file);
2115 if (
_languages.empty()) UserError(
"No available language packs (invalid versions?)");
2119 if (lang ==
nullptr) lang =
"en_GB";
2131 chosen_language = &lng;
2135 if (strcmp (lng.isocode,
"en_GB") == 0) en_GB_fallback = &lng;
2138 if (!lng.IsReasonablyFinished())
continue;
2140 if (strncmp(lng.isocode, lang, 5) == 0) chosen_language = &lng;
2141 if (strncmp(lng.isocode, lang, 2) == 0) language_fallback = &lng;
2146 if (chosen_language ==
nullptr) {
2147 chosen_language = (language_fallback !=
nullptr) ? language_fallback : en_GB_fallback;
2150 if (!
ReadLanguagePack(chosen_language)) UserError(
"Can't read language pack '{}'", chosen_language->
file);
2159 return _langpack.langpack->isocode;
2169 const Sprite *question_mark[FS_END];
2172 question_mark[size] =
GetGlyph(size,
'?');
2177 auto src = text->cbegin();
2180 while (src != text->cend()) {
2181 char32_t c = Utf8Consume(src);
2183 if (c >= SCC_FIRST_FONT && c <= SCC_LAST_FONT) {
2184 size = (
FontSize)(c - SCC_FIRST_FONT);
2187 std::string size_name;
2190 case FS_NORMAL: size_name =
"medium";
break;
2191 case FS_SMALL: size_name =
"small";
break;
2192 case FS_LARGE: size_name =
"large";
break;
2193 case FS_MONO: size_name =
"mono";
break;
2194 default: NOT_REACHED();
2197 Debug(fontcache, 0,
"Font is missing glyphs to display char 0x{:X} in {} font size", (
int)c, size_name);
2225 const char *ret = _langpack.offsets[_langpack.
langtab_start[this->
i] + this->
j];
2228 while (this->i < TEXT_TAB_END && this->
j >= _langpack.
langtab_num[this->i]) {
2241 void SetFontNames([[maybe_unused]]
FontCacheSettings *
settings, [[maybe_unused]]
const char *font_name, [[maybe_unused]]
const void *os_data)
override
2243 #if defined(WITH_FREETYPE) || defined(_WIN32) || defined(WITH_COCOA)
2248 settings->small.os_handle = os_data;
2249 settings->medium.os_handle = os_data;
2250 settings->large.os_handle = os_data;
2271 if (searcher ==
nullptr) searcher = &pack_searcher;
2273 #if defined(WITH_FREETYPE) || defined(_WIN32) || defined(WITH_COCOA)
2277 bool any_font_configured = !_fcsettings.
medium.
font.empty();
2283 bad_font = !
SetFallbackFont(&_fcsettings, _langpack.langpack->isocode, _langpack.langpack->winlangid, searcher);
2285 _fcsettings = backup;
2287 if (!bad_font && any_font_configured) {
2294 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.");
2300 if (bad_font && base_font) {
2315 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.");
2328 #if !(defined(WITH_ICU_I18N) && defined(WITH_HARFBUZZ)) && !defined(WITH_UNISCRIBE) && !defined(WITH_COCOA)
2343 static std::string err_str(
"XXXThis version of OpenTTD does not support right-to-left languages. Recompile with ICU + Harfbuzz enabled.");