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));
380 static const char *GetDecimalSeparator()
383 if (
StrEmpty(decimal_separator)) decimal_separator = _langpack.langpack->digit_decimal_separator;
384 return decimal_separator;
396 static const int max_digits = 20;
397 uint64_t divisor = 10000000000000000000ULL;
398 int thousands_offset = (max_digits - 1) % 3;
405 uint64_t num = number;
407 for (
int i = 0; i < max_digits; i++) {
409 if (num >= divisor) {
410 quot = num / divisor;
413 if ((tot |= quot) || i == max_digits - 1) {
414 builder +=
'0' + quot;
415 if ((i % 3) == thousands_offset && i < max_digits - 1) builder += separator;
422 static void FormatCommaNumber(
StringBuilder &builder, int64_t number)
425 if (
StrEmpty(separator)) separator = _langpack.langpack->digit_group_separator;
429 static void FormatNoCommaNumber(
StringBuilder &builder, int64_t number)
431 fmt::format_to(builder,
"{}", number);
434 static void FormatZerofillNumber(
StringBuilder &builder, int64_t number,
int count)
436 fmt::format_to(builder,
"{:0{}d}", number, count);
439 static void FormatHexNumber(
StringBuilder &builder, uint64_t number)
441 fmt::format_to(builder,
"0x{:X}", number);
454 const char *
const iec_prefixes[] = {
"",
"Ki",
"Mi",
"Gi",
"Ti",
"Pi",
"Ei"};
456 while (number >= 1024 * 1024) {
463 fmt::format_to(builder,
"{}", number);
464 }
else if (number < 1024 * 10) {
465 fmt::format_to(builder,
"{}{}{:02}", number / 1024, GetDecimalSeparator(), (number % 1024) * 100 / 1024);
466 }
else if (number < 1024 * 100) {
467 fmt::format_to(builder,
"{}{}{:01}", number / 1024, GetDecimalSeparator(), (number % 1024) * 10 / 1024);
469 assert(number < 1024 * 1024);
470 fmt::format_to(builder,
"{}", number / 1024);
473 assert(
id <
lengthof(iec_prefixes));
474 fmt::format_to(builder,
NBSP "{}B", iec_prefixes[
id]);
477 static void FormatYmdString(
StringBuilder &builder, TimerGameCalendar::Date date, uint case_index)
481 auto tmp_params = MakeParameters(ymd.day + STR_DAY_NUMBER_1ST - 1, STR_MONTH_ABBREV_JAN + ymd.month, ymd.year);
482 FormatString(builder, GetStringPtr(STR_FORMAT_DATE_LONG), tmp_params, case_index);
485 static void FormatMonthAndYear(
StringBuilder &builder, TimerGameCalendar::Date date, uint case_index)
489 auto tmp_params = MakeParameters(STR_MONTH_JAN + ymd.month, ymd.year);
490 FormatString(builder, GetStringPtr(STR_FORMAT_DATE_SHORT), tmp_params, case_index);
498 auto tmp_params = MakeParameters(ymd.day, 2, ymd.month + 1, 2, ymd.year);
506 bool negative = number < 0;
508 number *= spec->
rate;
529 if (number >=
Money(1
'000'000
'000'000
'000) - 500'000
'000) {
530 number = (number + Money(500'000
'000'000)) /
Money(1
'000'000
'000'000);
531 number_str = STR_CURRENCY_SHORT_TERA;
532 }
else if (number >=
Money(1
'000'000
'000'000) - 500
'000) {
533 number = (number + 500'000
'000) / 1'000
'000'000;
534 number_str = STR_CURRENCY_SHORT_GIGA;
535 }
else if (number >= 1
'000'000
'000 - 500) {
536 number = (number + 500'000) / 1
'000'000;
537 number_str = STR_CURRENCY_SHORT_MEGA;
538 }
else if (number >= 1
'000'000) {
539 number = (number + 500) / 1
'000;
540 number_str = STR_CURRENCY_SHORT_KILO;
544 const char *separator = _settings_game.locale.digit_group_separator_currency.c_str();
545 if (StrEmpty(separator)) separator = _currency->separator.c_str();
546 if (StrEmpty(separator)) separator = _langpack.langpack->digit_group_separator_currency;
547 FormatNumber(builder, number, separator);
548 if (number_str != STR_NULL) {
549 auto tmp_params = ArrayStringParameters<0>();
550 FormatString(builder, GetStringPtr(number_str), tmp_params);
553 /* Add suffix part, following symbol_pos specification.
554 * Here, it can can be either 1 (suffix) or 2 (both prefix and suffix).
555 * The only remaining value is 1 (prefix), so everything that is not 0 */
556 if (spec->symbol_pos != 0) builder += spec->suffix;
559 builder.Utf8Encode(SCC_POP_COLOUR);
569 static int DeterminePluralForm(int64_t count, int plural_form)
571 /* The absolute value determines plurality */
572 uint64_t n = abs(count);
574 switch (plural_form) {
578 /* Two forms: singular used for one only.
580 * Danish, Dutch, English, German, Norwegian, Swedish, Estonian, Finnish,
581 * Greek, Hebrew, Italian, Portuguese, Spanish, Esperanto */
583 return n != 1 ? 1 : 0;
587 * Hungarian, Japanese, Turkish */
591 /* Two forms: singular used for 0 and 1.
593 * French, Brazilian Portuguese */
595 return n > 1 ? 1 : 0;
597 /* Three forms: special cases for 0, and numbers ending in 1 except when ending in 11.
598 * Note: Cases are out of order for hysterical reasons. '0
' is last.
602 return n % 10 == 1 && n % 100 != 11 ? 0 : n != 0 ? 1 : 2;
604 /* Five forms: special cases for 1, 2, 3 to 6, and 7 to 10.
608 return n == 1 ? 0 : n == 2 ? 1 : n < 7 ? 2 : n < 11 ? 3 : 4;
610 /* 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.
614 return n % 10 == 1 && n % 100 != 11 ? 0 : n % 10 >= 2 && (n % 100 < 10 || n % 100 >= 20) ? 1 : 2;
616 /* 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.
618 * Croatian, Russian, Ukrainian */
620 return n % 10 == 1 && n % 100 != 11 ? 0 : n % 10 >= 2 && n % 10 <= 4 && (n % 100 < 10 || n % 100 >= 20) ? 1 : 2;
622 /* Three forms: special cases for 1, and numbers ending in 2 to 4 except when ending in 12 to 14.
626 return n == 1 ? 0 : n % 10 >= 2 && n % 10 <= 4 && (n % 100 < 10 || n % 100 >= 20) ? 1 : 2;
628 /* Four forms: special cases for numbers ending in 01, 02, and 03 to 04.
632 return n % 100 == 1 ? 0 : n % 100 == 2 ? 1 : n % 100 == 3 || n % 100 == 4 ? 2 : 3;
634 /* Two forms: singular used for numbers ending in 1 except when ending in 11.
638 return n % 10 == 1 && n % 100 != 11 ? 0 : 1;
640 /* Three forms: special cases for 1, and 2 to 4
644 return n == 1 ? 0 : n >= 2 && n <= 4 ? 1 : 2;
646 /* Two forms: cases for numbers ending with a consonant, and with a vowel.
647 * Korean doesn't have the concept of plural, but depending on how a
648 * number is pronounced it needs another version of a particle.
649 * As such the plural system is misused to give
this distinction.
675 return (n == 1 ? 0 : n == 0 || (n % 100 > 1 && n % 100 < 11) ? 1 : (n % 100 > 10 && n % 100 < 20) ? 2 : 3);
680 return ((n == 1 || n == 11) ? 0 : (n == 2 || n == 12) ? 1 : ((n > 2 && n < 11) || (n > 12 && n < 20)) ? 2 : 3);
686 return n == 1 ? 0 : (n == 0 || (n % 100 > 0 && n % 100 < 20)) ? 1 : 2;
690 static const char *ParseStringChoice(
const char *b, uint form,
StringBuilder &builder)
694 uint pos, i, mypos = 0;
696 for (i = pos = 0; i != n; i++) {
697 uint len = (byte)*b++;
698 if (i == form) mypos = pos;
702 builder += b + mypos;
716 int64_t
ToDisplay(int64_t input,
bool round =
true)
const
719 ? (int64_t)std::round(input * this->factor)
720 : (int64_t)(input * this->factor);
730 int64_t
FromDisplay(int64_t input,
bool round =
true, int64_t divider = 1)
const
733 ? (int64_t)std::round(input / this->factor / divider)
734 : (int64_t)(input / this->factor / divider);
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.578125 }, STR_UNITS_VELOCITY_GAMEUNITS_DAY, 1 },
759 { { 0.868976 }, STR_UNITS_VELOCITY_KNOTS, 0 },
764 { { 1.0 }, STR_UNITS_VELOCITY_IMPERIAL, 0 },
765 { { 1.609344 }, STR_UNITS_VELOCITY_METRIC, 0 },
766 { { 0.44704 }, STR_UNITS_VELOCITY_SI, 0 },
767 { { 0.289352 }, STR_UNITS_VELOCITY_GAMEUNITS_SEC, 1 },
768 { { 0.868976 }, STR_UNITS_VELOCITY_KNOTS, 0 },
773 { { 1.0 }, STR_UNITS_POWER_IMPERIAL, 0 },
774 { { 1.01387 }, STR_UNITS_POWER_METRIC, 0 },
775 { { 0.745699 }, STR_UNITS_POWER_SI, 0 },
780 { { 0.907185 }, STR_UNITS_POWER_IMPERIAL_TO_WEIGHT_IMPERIAL, 1 },
781 { { 1.0 }, STR_UNITS_POWER_IMPERIAL_TO_WEIGHT_METRIC, 1 },
782 { { 1.0 }, STR_UNITS_POWER_IMPERIAL_TO_WEIGHT_SI, 1 },
783 { { 0.919768 }, STR_UNITS_POWER_METRIC_TO_WEIGHT_IMPERIAL, 1 },
784 { { 1.01387 }, STR_UNITS_POWER_METRIC_TO_WEIGHT_METRIC, 1 },
785 { { 1.01387 }, STR_UNITS_POWER_METRIC_TO_WEIGHT_SI, 1 },
786 { { 0.676487 }, STR_UNITS_POWER_SI_TO_WEIGHT_IMPERIAL, 1 },
787 { { 0.745699 }, STR_UNITS_POWER_SI_TO_WEIGHT_METRIC, 1 },
788 { { 0.745699 }, STR_UNITS_POWER_SI_TO_WEIGHT_SI, 1 },
793 { { 1.102311 }, STR_UNITS_WEIGHT_SHORT_IMPERIAL, STR_UNITS_WEIGHT_LONG_IMPERIAL, 0 },
794 { { 1.0 }, STR_UNITS_WEIGHT_SHORT_METRIC, STR_UNITS_WEIGHT_LONG_METRIC, 0 },
795 { { 1000.0 }, STR_UNITS_WEIGHT_SHORT_SI, STR_UNITS_WEIGHT_LONG_SI, 0 },
800 { { 264.172 }, STR_UNITS_VOLUME_SHORT_IMPERIAL, STR_UNITS_VOLUME_LONG_IMPERIAL, 0 },
801 { { 1000.0 }, STR_UNITS_VOLUME_SHORT_METRIC, STR_UNITS_VOLUME_LONG_METRIC, 0 },
802 { { 1.0 }, STR_UNITS_VOLUME_SHORT_SI, STR_UNITS_VOLUME_LONG_SI, 0 },
807 { { 0.224809 }, STR_UNITS_FORCE_IMPERIAL, 0 },
808 { { 0.101972 }, STR_UNITS_FORCE_METRIC, 0 },
809 { { 0.001 }, STR_UNITS_FORCE_SI, 0 },
814 { { 3.0 }, STR_UNITS_HEIGHT_IMPERIAL, 0 },
815 { { 1.0 }, STR_UNITS_HEIGHT_METRIC, 0 },
816 { { 1.0 }, STR_UNITS_HEIGHT_SI, 0 },
821 { { 1 }, STR_UNITS_DAYS, 0 },
822 { { 2 }, STR_UNITS_SECONDS, 0 },
827 { { 1 }, STR_UNITS_MONTHS, 0 },
828 { { 1 }, STR_UNITS_MINUTES, 0 },
833 { { 1 }, STR_UNITS_YEARS, 0 },
834 { { 1 }, STR_UNITS_PERIODS, 0 },
839 { { 1 }, STR_UNITS_YEARS, 0 },
840 { { 12 }, STR_UNITS_MINUTES, 0 },
933 FormatString(dry_run_builder, str_arg, args, case_index, game_script,
true);
936 FormatString(dry_run_builder, str_arg, args, case_index, game_script,
true);
942 uint next_substr_case_index = 0;
943 std::stack<const char *, std::vector<const char *>> str_stack;
944 str_stack.push(str_arg);
948 while (!str_stack.empty() && (b = Utf8Consume(&str_stack.top())) ==
'\0') {
951 if (str_stack.empty())
break;
952 const char *&str = str_stack.top();
958 if (b == 0)
continue;
961 if (b < SCC_CONTROL_START || b > SCC_CONTROL_END) {
966 args.SetTypeOfNextParameter(b);
972 uint32_t stringid = std::strtoul(str, &p, 16);
973 if (*p !=
':' && *p !=
'\0') {
974 while (*p !=
'\0') p++;
976 builder +=
"(invalid SCC_ENCODED)";
980 while (*p !=
'\0') p++;
982 builder +=
"(invalid StringID)";
987 while (*p !=
'\0' && i < 20) {
992 bool instring =
false;
999 if (*p ==
'"' && escape) {
1006 instring = !instring;
1013 if (*p ==
':')
break;
1014 if (*p ==
'\0')
break;
1021 bool lookup = (l == SCC_ENCODED);
1022 if (lookup) s += len;
1024 param = std::strtoull(s, &p, 16);
1028 while (*p !=
'\0') p++;
1030 builder +=
"(invalid sub-StringID)";
1036 sub_args.SetParam(i++, param);
1039 sub_args.SetParam(i++, std::string(s, p - s - 1));
1051 StringID substr = Utf8Consume(&str);
1052 str_stack.push(GetStringPtr(substr));
1058 str_stack.push(GetStringPtr(substr));
1059 case_index = next_substr_case_index;
1060 next_substr_case_index = 0;
1065 case SCC_GENDER_LIST: {
1067 size_t offset = orig_offset + (byte)*str++;
1087 const char *s = buffer.c_str();
1088 char32_t c = Utf8Consume(&s);
1090 if (c == SCC_GENDER_INDEX) gender = (byte)s[0];
1092 str = ParseStringChoice(str, gender, builder);
1098 case SCC_GENDER_INDEX:
1107 case SCC_PLURAL_LIST: {
1108 int plural_form = *str++;
1109 size_t offset = orig_offset + (byte)*str++;
1110 int64_t v = args.GetParam(offset);
1115 case SCC_ARG_INDEX: {
1116 args.
SetOffset(orig_offset + (
byte)*str++);
1120 case SCC_SET_CASE: {
1123 next_substr_case_index = (byte)*str++;
1127 case SCC_SWITCH_CASE: {
1130 uint num = (byte)*str++;
1132 if ((
byte)str[0] == case_index) {
1138 str += 3 + (str[1] << 8) + str[2];
1145 builder += _openttd_revision;
1148 case SCC_RAW_STRING_POINTER: {
1151 if (raw_string ==
nullptr) {
1152 builder +=
"(invalid RAW_STRING parameter)";
1164 GetStringWithArgs(builder, string_id, tmp_params, next_substr_case_index, game_script);
1165 next_substr_case_index = 0;
1179 uint size = b - SCC_STRING1 + 1;
1181 builder +=
"(too many parameters)";
1184 GetStringWithArgs(builder, string_id, sub_args, next_substr_case_index, game_script);
1187 next_substr_case_index = 0;
1199 FormatCommaNumber(builder, number);
1204 int64_t fractional = number % divisor;
1206 FormatCommaNumber(builder, number);
1207 fmt::format_to(builder,
"{}{:0{}d}", GetDecimalSeparator(), fractional, digits);
1215 case SCC_ZEROFILL_NUM: {
1229 case SCC_CARGO_TINY: {
1238 switch (cargo_str) {
1253 FormatCommaNumber(builder, amount);
1257 case SCC_CARGO_SHORT: {
1265 switch (cargo_str) {
1269 auto tmp_params = MakeParameters(x.c.ToDisplay(args.
GetNextParameter<int64_t>()), x.decimal_places);
1277 auto tmp_params = MakeParameters(x.c.ToDisplay(args.
GetNextParameter<int64_t>()), x.decimal_places);
1291 case SCC_CARGO_LONG: {
1302 case SCC_CARGO_LIST: {
1307 if (!
HasBit(cmask, cs->Index()))
continue;
1316 GetStringWithArgs(builder, cs->name, args, next_substr_case_index, game_script);
1320 if (first)
GetStringWithArgs(builder, STR_JUST_NOTHING, args, next_substr_case_index, game_script);
1322 next_substr_case_index = 0;
1326 case SCC_CURRENCY_SHORT:
1327 FormatGenericCurrency(builder, _currency, args.
GetNextParameter<int64_t>(),
true);
1330 case SCC_CURRENCY_LONG:
1331 FormatGenericCurrency(builder, _currency, args.
GetNextParameter<int64_t>(),
false);
1335 FormatTinyOrISODate(builder, args.
GetNextParameter<TimerGameCalendar::Date>(), STR_FORMAT_DATE_TINY);
1338 case SCC_DATE_SHORT:
1339 FormatMonthAndYear(builder, args.
GetNextParameter<TimerGameCalendar::Date>(), next_substr_case_index);
1340 next_substr_case_index = 0;
1344 FormatYmdString(builder, args.
GetNextParameter<TimerGameCalendar::Date>(), next_substr_case_index);
1345 next_substr_case_index = 0;
1349 FormatTinyOrISODate(builder, args.
GetNextParameter<TimerGameCalendar::Date>(), STR_FORMAT_DATE_ISO);
1355 auto tmp_params = MakeParameters(x.c.ToDisplay(args.
GetNextParameter<int64_t>()), x.decimal_places);
1363 auto tmp_params = MakeParameters(x.c.ToDisplay(args.
GetNextParameter<int64_t>()), x.decimal_places);
1371 auto tmp_params = MakeParameters(x.c.ToDisplay(args.
GetNextParameter<int64_t>()), x.decimal_places);
1376 case SCC_POWER_TO_WEIGHT: {
1380 auto tmp_params = MakeParameters(x.c.ToDisplay(args.
GetNextParameter<int64_t>()), x.decimal_places);
1385 case SCC_VELOCITY: {
1395 case SCC_VOLUME_SHORT: {
1398 auto tmp_params = MakeParameters(x.c.ToDisplay(args.
GetNextParameter<int64_t>()), x.decimal_places);
1403 case SCC_VOLUME_LONG: {
1406 auto tmp_params = MakeParameters(x.c.ToDisplay(args.
GetNextParameter<int64_t>()), x.decimal_places);
1411 case SCC_WEIGHT_SHORT: {
1414 auto tmp_params = MakeParameters(x.c.ToDisplay(args.
GetNextParameter<int64_t>()), x.decimal_places);
1419 case SCC_WEIGHT_LONG: {
1422 auto tmp_params = MakeParameters(x.c.ToDisplay(args.
GetNextParameter<int64_t>()), x.decimal_places);
1427 case SCC_UNITS_DAYS_OR_SECONDS: {
1430 auto tmp_params = MakeParameters(x.c.ToDisplay(args.
GetNextParameter<int64_t>()), x.decimal_places);
1435 case SCC_UNITS_MONTHS_OR_MINUTES: {
1438 auto tmp_params = MakeParameters(x.c.ToDisplay(args.
GetNextParameter<int64_t>()), x.decimal_places);
1443 case SCC_UNITS_YEARS_OR_PERIODS: {
1446 auto tmp_params = MakeParameters(x.c.ToDisplay(args.
GetNextParameter<int64_t>()), x.decimal_places);
1451 case SCC_UNITS_YEARS_OR_MINUTES: {
1454 auto tmp_params = MakeParameters(x.c.ToDisplay(args.
GetNextParameter<int64_t>()), x.decimal_places);
1459 case SCC_COMPANY_NAME: {
1461 if (c ==
nullptr)
break;
1463 if (!c->
name.empty()) {
1464 auto tmp_params = MakeParameters(c->
name);
1467 auto tmp_params = MakeParameters(c->
name_2);
1473 case SCC_COMPANY_NUM: {
1478 auto tmp_params = MakeParameters(company + 1);
1484 case SCC_DEPOT_NAME: {
1493 if (!d->name.empty()) {
1494 auto tmp_params = MakeParameters(d->name);
1497 auto tmp_params = MakeParameters(d->town->
index, d->
town_cn + 1);
1503 case SCC_ENGINE_NAME: {
1506 if (e ==
nullptr)
break;
1508 if (!e->name.empty() && e->IsEnabled()) {
1509 auto tmp_params = MakeParameters(e->name);
1518 const GRFFile *grffile = e->GetGRF();
1519 assert(grffile !=
nullptr);
1535 case SCC_GROUP_NAME: {
1537 if (g ==
nullptr)
break;
1539 if (!g->
name.empty()) {
1540 auto tmp_params = MakeParameters(g->
name);
1543 auto tmp_params = MakeParameters(g->
index);
1549 case SCC_INDUSTRY_NAME: {
1551 if (i ==
nullptr)
break;
1553 static bool use_cache =
true;
1559 }
else if (use_cache) {
1561 builder += i->GetCachedName();
1565 FormatString(builder, GetStringPtr(STR_FORMAT_INDUSTRY_NAME), tmp_params, next_substr_case_index);
1567 next_substr_case_index = 0;
1571 case SCC_PRESIDENT_NAME: {
1573 if (c ==
nullptr)
break;
1585 case SCC_STATION_NAME: {
1589 if (st ==
nullptr) {
1598 static bool use_cache =
true;
1601 builder += st->GetCachedName();
1602 }
else if (!st->name.empty()) {
1603 auto tmp_params = MakeParameters(st->name);
1606 StringID string_id = st->string_id;
1607 if (st->indtype != IT_INVALID) {
1619 auto tmp_params = MakeParameters(STR_TOWN_NAME, st->town->index, st->index);
1625 case SCC_TOWN_NAME: {
1627 if (t ==
nullptr)
break;
1629 static bool use_cache =
true;
1632 builder += t->GetCachedName();
1633 }
else if (!t->
name.empty()) {
1634 auto tmp_params = MakeParameters(t->
name);
1642 case SCC_WAYPOINT_NAME: {
1644 if (wp ==
nullptr)
break;
1646 if (!wp->
name.empty()) {
1647 auto tmp_params = MakeParameters(wp->
name);
1651 StringID string_id = ((wp->
string_id == STR_SV_STNAME_BUOY) ? STR_FORMAT_BUOY_NAME : STR_FORMAT_WAYPOINT_NAME);
1652 if (wp->
town_cn != 0) string_id++;
1658 case SCC_VEHICLE_NAME: {
1660 if (v ==
nullptr)
break;
1662 if (!v->
name.empty()) {
1663 auto tmp_params = MakeParameters(v->
name);
1670 auto tmp_params = MakeParameters(v->
unitnumber);
1674 default: string_id = STR_INVALID_VEHICLE;
break;
1675 case VEH_TRAIN: string_id = STR_SV_TRAIN_NAME;
break;
1676 case VEH_ROAD: string_id = STR_SV_ROAD_VEHICLE_NAME;
break;
1677 case VEH_SHIP: string_id = STR_SV_SHIP_NAME;
break;
1678 case VEH_AIRCRAFT: string_id = STR_SV_AIRCRAFT_NAME;
break;
1686 case SCC_SIGN_NAME: {
1688 if (si ==
nullptr)
break;
1690 if (!si->name.empty()) {
1691 auto tmp_params = MakeParameters(si->name);
1700 case SCC_STATION_FEATURES: {
1715 }
catch (std::out_of_range &e) {
1716 Debug(misc, 0,
"FormatString: {}", e.what());
1717 builder +=
"(invalid parameter)";
1732 static void GetSpecialTownNameString(
StringBuilder &builder,
int ind, uint32_t seed)
1737 static const char *
const _silly_company_names[] = {
1739 "Tiny Transport Ltd.",
1741 "Comfy-Coach & Co.",
1742 "Crush & Bump Ltd.",
1743 "Broken & Late Ltd.",
1745 "Supersonic Travel",
1747 "Lightning International",
1748 "Pannik & Loozit Ltd.",
1749 "Inter-City Transport",
1750 "Getout & Pushit Ltd."
1753 static const char *
const _surname_list[] = {
1785 static const char *
const _silly_surname_list[] = {
1800 static const char _initial_name_letters[] = {
1801 'A',
'B',
'C',
'D',
'E',
'F',
'G',
'H',
'I',
'J',
1802 'K',
'L',
'M',
'N',
'P',
'R',
'S',
'T',
'W',
1805 static void GenAndCoName(
StringBuilder &builder, uint32_t arg)
1807 const char *
const *base;
1811 base = _silly_surname_list;
1812 num =
lengthof(_silly_surname_list);
1814 base = _surname_list;
1818 builder += base[num *
GB(arg, 16, 8) >> 8];
1819 builder +=
" & Co.";
1822 static void GenPresidentName(
StringBuilder &builder, uint32_t x)
1824 char initial[] =
"?. ";
1825 const char *
const *base;
1829 initial[0] = _initial_name_letters[
sizeof(_initial_name_letters) *
GB(x, 0, 8) >> 8];
1832 i = (
sizeof(_initial_name_letters) + 35) *
GB(x, 8, 8) >> 8;
1833 if (i <
sizeof(_initial_name_letters)) {
1834 initial[0] = _initial_name_letters[i];
1839 base = _silly_surname_list;
1840 num =
lengthof(_silly_surname_list);
1842 base = _surname_list;
1846 builder += base[num *
GB(x, 16, 8) >> 8];
1853 builder += _silly_company_names[std::min<uint>(args.
GetNextParameter<uint16_t>(),
lengthof(_silly_company_names) - 1)];
1866 if (
IsInsideMM(ind - 6, 0, SPECSTR_TOWNNAME_LAST - SPECSTR_TOWNNAME_START + 1)) {
1867 GetSpecialTownNameString(builder, ind - 6, args.
GetNextParameter<uint32_t>());
1868 builder +=
" Transport";
1882 this->
version == TO_LE32(LANGUAGE_PACK_VERSION) &&
1902 return 4 * this->
missing < LANGUAGE_TOTAL_STRINGS;
1914 std::unique_ptr<LanguagePack, LanguagePackDeleter> lang_pack(
reinterpret_cast<LanguagePack *
>(
ReadFileToMem(lang->
file.string(), len, 1U << 20).release()));
1915 if (!lang_pack)
return false;
1918 const char *end = (
char *)lang_pack.get() + len + 1;
1921 if (end <= lang_pack->data || !lang_pack->IsValid()) {
1925 std::array<uint, TEXT_TAB_END> tab_start, tab_num;
1929 uint16_t num = FROM_LE16(lang_pack->offsets[i]);
1932 tab_start[i] = count;
1938 std::vector<char *> offs(count);
1941 char *s = lang_pack->data;
1943 for (uint i = 0; i < count; i++) {
1944 if (s + len >= end)
return false;
1947 len = ((len & 0x3F) << 8) + (byte)*s++;
1948 if (s + len >= end)
return false;
1956 _langpack.langpack = std::move(lang_pack);
1957 _langpack.offsets = std::move(offs);
1967 extern void Win32SetCurrentLocaleName(std::string iso_code);
1976 #ifdef WITH_ICU_I18N
1978 UErrorCode status = U_ZERO_ERROR;
1983 if (U_FAILURE(status)) {
2009 #if !(defined(_WIN32) || defined(__APPLE__))
2022 env = std::getenv(
"LANGUAGE");
2023 if (env !=
nullptr)
return env;
2025 env = std::getenv(
"LC_ALL");
2026 if (env !=
nullptr)
return env;
2028 if (param !=
nullptr) {
2029 env = std::getenv(param);
2030 if (env !=
nullptr)
return env;
2033 return std::getenv(
"LANG");
2047 if (newgrflangid == lang.newgrflangid)
return ⟨
2061 FILE *f = fopen(file,
"rb");
2062 if (f ==
nullptr)
return false;
2064 size_t read = fread(hdr,
sizeof(*hdr), 1, f);
2067 bool ret = read == 1 && hdr->
IsValid();
2084 if (dir !=
nullptr) {
2085 struct dirent *dirent;
2086 while ((dirent = readdir(dir)) !=
nullptr) {
2087 std::string d_name =
FS2OTTD(dirent->d_name);
2088 const char *extension = strrchr(d_name.c_str(),
'.');
2091 if (extension ==
nullptr || strcmp(extension,
".lng") != 0)
continue;
2094 lmd.
file = path + d_name;
2098 Debug(misc, 3,
"{} is not a valid language file", lmd.
file);
2100 Debug(misc, 3,
"{}'s language ID is already known", lmd.
file);
2118 if (
_languages.empty()) UserError(
"No available language packs (invalid versions?)");
2122 if (lang ==
nullptr) lang =
"en_GB";
2134 chosen_language = &lng;
2138 if (strcmp (lng.isocode,
"en_GB") == 0) en_GB_fallback = &lng;
2141 if (!lng.IsReasonablyFinished())
continue;
2143 if (strncmp(lng.isocode, lang, 5) == 0) chosen_language = &lng;
2144 if (strncmp(lng.isocode, lang, 2) == 0) language_fallback = &lng;
2149 if (chosen_language ==
nullptr) {
2150 chosen_language = (language_fallback !=
nullptr) ? language_fallback : en_GB_fallback;
2153 if (!
ReadLanguagePack(chosen_language)) UserError(
"Can't read language pack '{}'", chosen_language->
file);
2162 return _langpack.langpack->isocode;
2172 const Sprite *question_mark[FS_END];
2175 question_mark[size] =
GetGlyph(size,
'?');
2180 auto src = text->cbegin();
2183 while (src != text->cend()) {
2184 char32_t c = Utf8Consume(src);
2186 if (c >= SCC_FIRST_FONT && c <= SCC_LAST_FONT) {
2187 size = (
FontSize)(c - SCC_FIRST_FONT);
2190 std::string size_name;
2193 case FS_NORMAL: size_name =
"medium";
break;
2194 case FS_SMALL: size_name =
"small";
break;
2195 case FS_LARGE: size_name =
"large";
break;
2196 case FS_MONO: size_name =
"mono";
break;
2197 default: NOT_REACHED();
2200 Debug(fontcache, 0,
"Font is missing glyphs to display char 0x{:X} in {} font size", (
int)c, size_name);
2228 const char *ret = _langpack.offsets[_langpack.
langtab_start[this->
i] + this->
j];
2231 while (this->i < TEXT_TAB_END && this->
j >= _langpack.
langtab_num[this->i]) {
2244 void SetFontNames([[maybe_unused]]
FontCacheSettings *
settings, [[maybe_unused]]
const char *font_name, [[maybe_unused]]
const void *os_data)
override
2246 #if defined(WITH_FREETYPE) || defined(_WIN32) || defined(WITH_COCOA)
2251 settings->small.os_handle = os_data;
2252 settings->medium.os_handle = os_data;
2253 settings->large.os_handle = os_data;
2274 if (searcher ==
nullptr) searcher = &pack_searcher;
2276 #if defined(WITH_FREETYPE) || defined(_WIN32) || defined(WITH_COCOA)
2280 bool any_font_configured = !_fcsettings.
medium.
font.empty();
2286 bad_font = !
SetFallbackFont(&_fcsettings, _langpack.langpack->isocode, _langpack.langpack->winlangid, searcher);
2288 _fcsettings = backup;
2290 if (!bad_font && any_font_configured) {
2297 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.");
2303 if (bad_font && base_font) {
2318 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.");
2331 #if !(defined(WITH_ICU_I18N) && defined(WITH_HARFBUZZ)) && !defined(WITH_UNISCRIBE) && !defined(WITH_COCOA)
2346 static std::string err_str(
"XXXThis version of OpenTTD does not support right-to-left languages. Recompile with ICU + Harfbuzz enabled.");