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/>.
8 /** @file strings.cpp Handling of translated strings. */
12 #include "station_base.h"
14 #include "waypoint_base.h"
15 #include "depot_base.h"
17 #include "newgrf_text.h"
18 #include "fileio_func.h"
19 #include "signs_base.h"
20 #include "fontdetection.h"
22 #include "error_func.h"
23 #include "strings_func.h"
25 #include "core/endian_func.hpp"
26 #include "timer/timer_game_calendar.h"
27 #include "vehicle_base.h"
28 #include "engine_base.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"
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"
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.
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
;
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 ¶m
: this->parameters
) param
.type
= 0;
76 * Get the next parameter from our parameters.
77 * This updates the offset, so the next time this is called the next parameter
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 ¶m
= this->parameters
[this->offset
++];
89 if (param
.type
!= 0 && param
.type
!= this->next_type
) {
91 throw std::out_of_range("Trying to read string parameter with wrong type");
93 param
.type
= this->next_type
;
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
)
130 while (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
)
147 GetBroadestDigit(&front
, &next
, size
);
148 uint64_t val
= count
> 1 ? front
: next
;
149 for (; count
> 1; count
--) {
150 val
= 10 * val
+ next
;
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
)
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;
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
)
246 GetStringWithArgs(builder
, STR_UNDEFINED
, args
);
250 uint index
= GetStringIndex(string
);
251 StringTab tab
= GetStringTab(string
);
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
:
279 /* Old table for custom names. This is no longer used */
281 FatalError("Incorrect conversion of custom name string.");
285 case TEXT_TAB_GAMESCRIPT_START
: {
286 FormatString(builder
, GetGameStringPtr(index
), args
, case_index
, true);
290 case TEXT_TAB_OLD_NEWGRF
:
293 case TEXT_TAB_NEWGRF_START
: {
294 FormatString(builder
, GetGRFStringPtr(index
), args
, case_index
);
302 if (index
>= _langpack
.langtab_num
[tab
]) {
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 * Resolve the given StringID and append in place into an existing std::string with all the associated
327 * DParam lookups and formatting.
328 * @param result The std::string to place the translated string.
329 * @param string The unique identifier of the translatable string.
331 void AppendStringInPlace(std::string
&result
, StringID string
)
333 _global_string_params
.PrepareForNextRun();
334 StringBuilder
builder(result
);
335 GetStringWithArgs(builder
, string
, _global_string_params
);
339 * Get a parsed string with most special stringcodes replaced by the string parameters.
340 * @param string The ID of the string to parse.
341 * @param args Arguments for the string.
342 * @return The parsed string.
344 std::string
GetStringWithArgs(StringID string
, StringParameters
&args
)
347 StringBuilder
builder(result
);
348 GetStringWithArgs(builder
, string
, args
);
353 * This function is used to "bind" a C string to a OpenTTD dparam slot.
354 * @param n slot of the string
355 * @param str string to bind
357 void SetDParamStr(size_t n
, const char *str
)
359 _global_string_params
.SetParam(n
, str
);
363 * This function is used to "bind" the C string of a std::string to a OpenTTD dparam slot.
364 * The caller has to ensure that the std::string reference remains valid while the string is shown.
365 * @param n slot of the string
366 * @param str string to bind
368 void SetDParamStr(size_t n
, const std::string
&str
)
370 _global_string_params
.SetParam(n
, str
);
374 * This function is used to "bind" the std::string to a OpenTTD dparam slot.
375 * Contrary to the other \c SetDParamStr functions, this moves the string into
376 * the parameter slot.
377 * @param n slot of the string
378 * @param str string to bind
380 void SetDParamStr(size_t n
, std::string
&&str
)
382 _global_string_params
.SetParam(n
, std::move(str
));
385 static const char *GetDecimalSeparator()
387 const char *decimal_separator
= _settings_game
.locale
.digit_decimal_separator
.c_str();
388 if (StrEmpty(decimal_separator
)) decimal_separator
= _langpack
.langpack
->digit_decimal_separator
;
389 return decimal_separator
;
393 * Format a number into a string.
394 * @param builder the string builder to write to
395 * @param number the number to write down
396 * @param separator the thousands-separator to use
398 static void FormatNumber(StringBuilder
&builder
, int64_t number
, const char *separator
)
400 static const int max_digits
= 20;
401 uint64_t divisor
= 10000000000000000000ULL;
402 int thousands_offset
= (max_digits
- 1) % 3;
409 uint64_t num
= number
;
411 for (int i
= 0; i
< max_digits
; i
++) {
413 if (num
>= divisor
) {
414 quot
= num
/ divisor
;
417 if ((tot
|= quot
) || i
== max_digits
- 1) {
418 builder
+= '0' + quot
; // quot is a single digit
419 if ((i
% 3) == thousands_offset
&& i
< max_digits
- 1) builder
+= separator
;
426 static void FormatCommaNumber(StringBuilder
&builder
, int64_t number
)
428 const char *separator
= _settings_game
.locale
.digit_group_separator
.c_str();
429 if (StrEmpty(separator
)) separator
= _langpack
.langpack
->digit_group_separator
;
430 FormatNumber(builder
, number
, separator
);
433 static void FormatNoCommaNumber(StringBuilder
&builder
, int64_t number
)
435 fmt::format_to(builder
, "{}", number
);
438 static void FormatZerofillNumber(StringBuilder
&builder
, int64_t number
, int count
)
440 fmt::format_to(builder
, "{:0{}d}", number
, count
);
443 static void FormatHexNumber(StringBuilder
&builder
, uint64_t number
)
445 fmt::format_to(builder
, "0x{:X}", number
);
449 * Format a given number as a number of bytes with the SI prefix.
450 * @param builder the string builder to write to
451 * @param number the number of bytes to write down
453 static void FormatBytes(StringBuilder
&builder
, int64_t number
)
457 /* 1 2^10 2^20 2^30 2^40 2^50 2^60 */
458 const char * const iec_prefixes
[] = {"", "Ki", "Mi", "Gi", "Ti", "Pi", "Ei"};
460 while (number
>= 1024 * 1024) {
467 fmt::format_to(builder
, "{}", number
);
468 } else if (number
< 1024 * 10) {
469 fmt::format_to(builder
, "{}{}{:02}", number
/ 1024, GetDecimalSeparator(), (number
% 1024) * 100 / 1024);
470 } else if (number
< 1024 * 100) {
471 fmt::format_to(builder
, "{}{}{:01}", number
/ 1024, GetDecimalSeparator(), (number
% 1024) * 10 / 1024);
473 assert(number
< 1024 * 1024);
474 fmt::format_to(builder
, "{}", number
/ 1024);
477 assert(id
< lengthof(iec_prefixes
));
478 fmt::format_to(builder
, NBSP
"{}B", iec_prefixes
[id
]);
481 static void FormatYmdString(StringBuilder
&builder
, TimerGameCalendar::Date date
, uint case_index
)
483 TimerGameCalendar::YearMonthDay ymd
= TimerGameCalendar::ConvertDateToYMD(date
);
485 auto tmp_params
= MakeParameters(ymd
.day
+ STR_DAY_NUMBER_1ST
- 1, STR_MONTH_ABBREV_JAN
+ ymd
.month
, ymd
.year
);
486 FormatString(builder
, GetStringPtr(STR_FORMAT_DATE_LONG
), tmp_params
, case_index
);
489 static void FormatMonthAndYear(StringBuilder
&builder
, TimerGameCalendar::Date date
, uint case_index
)
491 TimerGameCalendar::YearMonthDay ymd
= TimerGameCalendar::ConvertDateToYMD(date
);
493 auto tmp_params
= MakeParameters(STR_MONTH_JAN
+ ymd
.month
, ymd
.year
);
494 FormatString(builder
, GetStringPtr(STR_FORMAT_DATE_SHORT
), tmp_params
, case_index
);
497 static void FormatTinyOrISODate(StringBuilder
&builder
, TimerGameCalendar::Date date
, StringID str
)
499 TimerGameCalendar::YearMonthDay ymd
= TimerGameCalendar::ConvertDateToYMD(date
);
501 /* Day and month are zero-padded with ZEROFILL_NUM, hence the two 2s. */
502 auto tmp_params
= MakeParameters(ymd
.day
, 2, ymd
.month
+ 1, 2, ymd
.year
);
503 FormatString(builder
, GetStringPtr(str
), tmp_params
);
506 static void FormatGenericCurrency(StringBuilder
&builder
, const CurrencySpec
*spec
, Money number
, bool compact
)
508 /* We are going to make number absolute for printing, so
509 * keep this piece of data as we need it later on */
510 bool negative
= number
< 0;
512 number
*= spec
->rate
;
514 /* convert from negative */
516 builder
.Utf8Encode(SCC_PUSH_COLOUR
);
517 builder
.Utf8Encode(SCC_RED
);
522 /* Add prefix part, following symbol_pos specification.
523 * Here, it can can be either 0 (prefix) or 2 (both prefix and suffix).
524 * The only remaining value is 1 (suffix), so everything that is not 1 */
525 if (spec
->symbol_pos
!= 1) builder
+= spec
->prefix
;
527 StringID number_str
= STR_NULL
;
529 /* For huge numbers, compact the number. */
531 /* Take care of the thousand rounding. Having 1 000 000 k
532 * and 1 000 M is inconsistent, so always use 1 000 M. */
533 if (number
>= Money(1'000'000'000'000'000) - 500'000'000) {
534 number
= (number
+ Money(500'000'000'000)) / Money(1'000'000'000'000);
535 number_str
= STR_CURRENCY_SHORT_TERA
;
536 } else if (number
>= Money(1'000'000'000'000) - 500'000) {
537 number
= (number
+ 500'000'000) / 1'000'000'000;
538 number_str
= STR_CURRENCY_SHORT_GIGA
;
539 } else if (number
>= 1'000'000'000 - 500) {
540 number
= (number
+ 500'000) / 1'000'000;
541 number_str
= STR_CURRENCY_SHORT_MEGA
;
542 } else if (number
>= 1'000'000) {
543 number
= (number
+ 500) / 1'000;
544 number_str
= STR_CURRENCY_SHORT_KILO
;
548 const char *separator
= _settings_game
.locale
.digit_group_separator_currency
.c_str();
549 if (StrEmpty(separator
)) separator
= GetCurrency().separator
.c_str();
550 if (StrEmpty(separator
)) separator
= _langpack
.langpack
->digit_group_separator_currency
;
551 FormatNumber(builder
, number
, separator
);
552 if (number_str
!= STR_NULL
) {
553 auto tmp_params
= ArrayStringParameters
<0>();
554 FormatString(builder
, GetStringPtr(number_str
), tmp_params
);
557 /* Add suffix part, following symbol_pos specification.
558 * Here, it can can be either 1 (suffix) or 2 (both prefix and suffix).
559 * The only remaining value is 1 (prefix), so everything that is not 0 */
560 if (spec
->symbol_pos
!= 0) builder
+= spec
->suffix
;
563 builder
.Utf8Encode(SCC_POP_COLOUR
);
568 * Determine the "plural" index given a plural form and a number.
569 * @param count The number to get the plural index of.
570 * @param plural_form The plural form we want an index for.
571 * @return The plural index for the given form.
573 static int DeterminePluralForm(int64_t count
, int plural_form
)
575 /* The absolute value determines plurality */
576 uint64_t n
= abs(count
);
578 switch (plural_form
) {
582 /* Two forms: singular used for one only.
584 * Danish, Dutch, English, German, Norwegian, Swedish, Estonian, Finnish,
585 * Greek, Hebrew, Italian, Portuguese, Spanish, Esperanto */
587 return n
!= 1 ? 1 : 0;
591 * Hungarian, Japanese, Turkish */
595 /* Two forms: singular used for 0 and 1.
597 * French, Brazilian Portuguese */
599 return n
> 1 ? 1 : 0;
601 /* Three forms: special cases for 0, and numbers ending in 1 except when ending in 11.
602 * Note: Cases are out of order for hysterical reasons. '0' is last.
606 return n
% 10 == 1 && n
% 100 != 11 ? 0 : n
!= 0 ? 1 : 2;
608 /* Five forms: special cases for 1, 2, 3 to 6, and 7 to 10.
612 return n
== 1 ? 0 : n
== 2 ? 1 : n
< 7 ? 2 : n
< 11 ? 3 : 4;
614 /* 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.
618 return n
% 10 == 1 && n
% 100 != 11 ? 0 : n
% 10 >= 2 && (n
% 100 < 10 || n
% 100 >= 20) ? 1 : 2;
620 /* 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.
622 * Croatian, Russian, Ukrainian */
624 return n
% 10 == 1 && n
% 100 != 11 ? 0 : n
% 10 >= 2 && n
% 10 <= 4 && (n
% 100 < 10 || n
% 100 >= 20) ? 1 : 2;
626 /* Three forms: special cases for 1, and numbers ending in 2 to 4 except when ending in 12 to 14.
630 return n
== 1 ? 0 : n
% 10 >= 2 && n
% 10 <= 4 && (n
% 100 < 10 || n
% 100 >= 20) ? 1 : 2;
632 /* Four forms: special cases for numbers ending in 01, 02, and 03 to 04.
636 return n
% 100 == 1 ? 0 : n
% 100 == 2 ? 1 : n
% 100 == 3 || n
% 100 == 4 ? 2 : 3;
638 /* Two forms: singular used for numbers ending in 1 except when ending in 11.
642 return n
% 10 == 1 && n
% 100 != 11 ? 0 : 1;
644 /* Three forms: special cases for 1, and 2 to 4
648 return n
== 1 ? 0 : n
>= 2 && n
<= 4 ? 1 : 2;
650 /* Two forms: cases for numbers ending with a consonant, and with a vowel.
651 * Korean doesn't have the concept of plural, but depending on how a
652 * number is pronounced it needs another version of a particle.
653 * As such the plural system is misused to give this distinction.
675 /* Four forms: special cases for 1, 0 and numbers ending in 02 to 10, and numbers ending in 11 to 19.
679 return (n
== 1 ? 0 : n
== 0 || (n
% 100 > 1 && n
% 100 < 11) ? 1 : (n
% 100 > 10 && n
% 100 < 20) ? 2 : 3);
680 /* Four forms: special cases for 1 and 11, 2 and 12, 3 .. 10 and 13 .. 19, other
684 return ((n
== 1 || n
== 11) ? 0 : (n
== 2 || n
== 12) ? 1 : ((n
> 2 && n
< 11) || (n
> 12 && n
< 20)) ? 2 : 3);
686 /* Three forms: special cases for 1, 0 and numbers ending in 01 to 19.
690 return n
== 1 ? 0 : (n
== 0 || (n
% 100 > 0 && n
% 100 < 20)) ? 1 : 2;
694 static const char *ParseStringChoice(const char *b
, uint form
, StringBuilder
&builder
)
696 /* <NUM> {Length of each string} {each string} */
697 uint n
= (uint8_t)*b
++;
698 uint pos
, i
, mypos
= 0;
700 for (i
= pos
= 0; i
!= n
; i
++) {
701 uint len
= (uint8_t)*b
++;
702 if (i
== form
) mypos
= pos
;
706 builder
+= b
+ mypos
;
710 /** Helper for unit conversion. */
711 struct UnitConversion
{
712 double factor
; ///< Amount to multiply or divide upon conversion.
715 * Convert value from OpenTTD's internal unit into the displayed value.
716 * @param input The input to convert.
717 * @param round Whether to round the value or not.
718 * @return The converted value.
720 int64_t ToDisplay(int64_t input
, bool round
= true) const
723 ? (int64_t)std::round(input
* this->factor
)
724 : (int64_t)(input
* this->factor
);
728 * Convert the displayed value back into a value of OpenTTD's internal unit.
729 * @param input The input to convert.
730 * @param round Whether to round the value up or not.
731 * @param divider Divide the return value by this.
732 * @return The converted value.
734 int64_t FromDisplay(int64_t input
, bool round
= true, int64_t divider
= 1) const
737 ? (int64_t)std::round(input
/ this->factor
/ divider
)
738 : (int64_t)(input
/ this->factor
/ divider
);
742 /** Information about a specific unit system. */
744 UnitConversion c
; ///< Conversion
745 StringID s
; ///< String for the unit
746 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.
749 /** Information about a specific unit system with a long variant. */
751 UnitConversion c
; ///< Conversion
752 StringID s
; ///< String for the short variant of the unit
753 StringID l
; ///< String for the long variant of the unit
754 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.
757 /** Unit conversions for velocity. */
758 static const Units _units_velocity_calendar
[] = {
759 { { 1.0 }, STR_UNITS_VELOCITY_IMPERIAL
, 0 },
760 { { 1.609344 }, STR_UNITS_VELOCITY_METRIC
, 0 },
761 { { 0.44704 }, STR_UNITS_VELOCITY_SI
, 0 },
762 { { 0.578125 }, STR_UNITS_VELOCITY_GAMEUNITS_DAY
, 1 },
763 { { 0.868976 }, STR_UNITS_VELOCITY_KNOTS
, 0 },
766 /** Unit conversions for velocity. */
767 static const Units _units_velocity_realtime
[] = {
768 { { 1.0 }, STR_UNITS_VELOCITY_IMPERIAL
, 0 },
769 { { 1.609344 }, STR_UNITS_VELOCITY_METRIC
, 0 },
770 { { 0.44704 }, STR_UNITS_VELOCITY_SI
, 0 },
771 { { 0.289352 }, STR_UNITS_VELOCITY_GAMEUNITS_SEC
, 1 },
772 { { 0.868976 }, STR_UNITS_VELOCITY_KNOTS
, 0 },
775 /** Unit conversions for power. */
776 static const Units _units_power
[] = {
777 { { 1.0 }, STR_UNITS_POWER_IMPERIAL
, 0 },
778 { { 1.01387 }, STR_UNITS_POWER_METRIC
, 0 },
779 { { 0.745699 }, STR_UNITS_POWER_SI
, 0 },
782 /** Unit conversions for power to weight. */
783 static const Units _units_power_to_weight
[] = {
784 { { 0.907185 }, STR_UNITS_POWER_IMPERIAL_TO_WEIGHT_IMPERIAL
, 1 },
785 { { 1.0 }, STR_UNITS_POWER_IMPERIAL_TO_WEIGHT_METRIC
, 1 },
786 { { 1.0 }, STR_UNITS_POWER_IMPERIAL_TO_WEIGHT_SI
, 1 },
787 { { 0.919768 }, STR_UNITS_POWER_METRIC_TO_WEIGHT_IMPERIAL
, 1 },
788 { { 1.01387 }, STR_UNITS_POWER_METRIC_TO_WEIGHT_METRIC
, 1 },
789 { { 1.01387 }, STR_UNITS_POWER_METRIC_TO_WEIGHT_SI
, 1 },
790 { { 0.676487 }, STR_UNITS_POWER_SI_TO_WEIGHT_IMPERIAL
, 1 },
791 { { 0.745699 }, STR_UNITS_POWER_SI_TO_WEIGHT_METRIC
, 1 },
792 { { 0.745699 }, STR_UNITS_POWER_SI_TO_WEIGHT_SI
, 1 },
795 /** Unit conversions for weight. */
796 static const UnitsLong _units_weight
[] = {
797 { { 1.102311 }, STR_UNITS_WEIGHT_SHORT_IMPERIAL
, STR_UNITS_WEIGHT_LONG_IMPERIAL
, 0 },
798 { { 1.0 }, STR_UNITS_WEIGHT_SHORT_METRIC
, STR_UNITS_WEIGHT_LONG_METRIC
, 0 },
799 { { 1000.0 }, STR_UNITS_WEIGHT_SHORT_SI
, STR_UNITS_WEIGHT_LONG_SI
, 0 },
802 /** Unit conversions for volume. */
803 static const UnitsLong _units_volume
[] = {
804 { { 264.172 }, STR_UNITS_VOLUME_SHORT_IMPERIAL
, STR_UNITS_VOLUME_LONG_IMPERIAL
, 0 },
805 { { 1000.0 }, STR_UNITS_VOLUME_SHORT_METRIC
, STR_UNITS_VOLUME_LONG_METRIC
, 0 },
806 { { 1.0 }, STR_UNITS_VOLUME_SHORT_SI
, STR_UNITS_VOLUME_LONG_SI
, 0 },
809 /** Unit conversions for force. */
810 static const Units _units_force
[] = {
811 { { 0.224809 }, STR_UNITS_FORCE_IMPERIAL
, 0 },
812 { { 0.101972 }, STR_UNITS_FORCE_METRIC
, 0 },
813 { { 0.001 }, STR_UNITS_FORCE_SI
, 0 },
816 /** Unit conversions for height. */
817 static const Units _units_height
[] = {
818 { { 3.0 }, STR_UNITS_HEIGHT_IMPERIAL
, 0 }, // "Wrong" conversion factor for more nicer GUI values
819 { { 1.0 }, STR_UNITS_HEIGHT_METRIC
, 0 },
820 { { 1.0 }, STR_UNITS_HEIGHT_SI
, 0 },
823 /** Unit conversions for time in calendar days or wallclock seconds */
824 static const Units _units_time_days_or_seconds
[] = {
825 { { 1 }, STR_UNITS_DAYS
, 0 },
826 { { 2 }, STR_UNITS_SECONDS
, 0 },
829 /** Unit conversions for time in calendar months or wallclock minutes */
830 static const Units _units_time_months_or_minutes
[] = {
831 { { 1 }, STR_UNITS_MONTHS
, 0 },
832 { { 1 }, STR_UNITS_MINUTES
, 0 },
835 /** Unit conversions for time in calendar years or economic periods */
836 static const Units _units_time_years_or_periods
[] = {
837 { { 1 }, STR_UNITS_YEARS
, 0 },
838 { { 1 }, STR_UNITS_PERIODS
, 0 },
841 /** Unit conversions for time in calendar years or wallclock minutes */
842 static const Units _units_time_years_or_minutes
[] = {
843 { { 1 }, STR_UNITS_YEARS
, 0 },
844 { { 12 }, STR_UNITS_MINUTES
, 0 },
848 * Get the correct velocity units depending on the vehicle type and whether we're using real-time units.
849 * @param type VehicleType to convert velocity for.
850 * @return The Units for the proper vehicle and time mode.
852 static const Units
GetVelocityUnits(VehicleType type
)
854 uint8_t setting
= (type
== VEH_SHIP
|| type
== VEH_AIRCRAFT
) ? _settings_game
.locale
.units_velocity_nautical
: _settings_game
.locale
.units_velocity
;
856 assert(setting
< lengthof(_units_velocity_calendar
));
857 assert(setting
< lengthof(_units_velocity_realtime
));
859 if (TimerGameEconomy::UsingWallclockUnits()) return _units_velocity_realtime
[setting
];
861 return _units_velocity_calendar
[setting
];
865 * Convert the given (internal) speed to the display speed.
866 * @param speed the speed to convert
867 * @return the converted speed.
869 uint
ConvertSpeedToDisplaySpeed(uint speed
, VehicleType type
)
871 /* For historical reasons we don't want to mess with the
872 * conversion for speed. So, don't round it and keep the
873 * original conversion factors instead of the real ones. */
874 return GetVelocityUnits(type
).c
.ToDisplay(speed
, false);
878 * Convert the given display speed to the (internal) speed.
879 * @param speed the speed to convert
880 * @return the converted speed.
882 uint
ConvertDisplaySpeedToSpeed(uint speed
, VehicleType type
)
884 return GetVelocityUnits(type
).c
.FromDisplay(speed
);
888 * Convert the given km/h-ish speed to the display speed.
889 * @param speed the speed to convert
890 * @return the converted speed.
892 uint
ConvertKmhishSpeedToDisplaySpeed(uint speed
, VehicleType type
)
894 return GetVelocityUnits(type
).c
.ToDisplay(speed
* 10, false) / 16;
898 * Convert the given display speed to the km/h-ish speed.
899 * @param speed the speed to convert
900 * @return the converted speed.
902 uint
ConvertDisplaySpeedToKmhishSpeed(uint speed
, VehicleType type
)
904 return GetVelocityUnits(type
).c
.FromDisplay(speed
* 16, true, 10);
908 * Parse most format codes within a string and write the result to a buffer.
909 * @param builder The string builder to write the final string to.
910 * @param str_arg The original string with format codes.
911 * @param args Pointer to extra arguments used by various string codes.
912 * @param dry_run True when the args' type data is not yet initialized.
914 static void FormatString(StringBuilder
&builder
, const char *str_arg
, StringParameters
&args
, uint case_index
, bool game_script
, bool dry_run
)
916 size_t orig_offset
= args
.GetOffset();
920 * This function is normally called with `dry_run` false, then we call this function again
921 * with `dry_run` being true. The dry run is required for the gender formatting. For the
922 * gender determination we need to format a sub string to get the gender, but for that we
923 * need to know as what string control code type the specific parameter is encoded. Since
924 * gendered words can be before the "parameter" words, this needs to be determined before
925 * the actual formatting.
928 StringBuilder
dry_run_builder(buffer
);
929 if (UsingNewGRFTextStack()) {
930 /* Values from the NewGRF text stack are only copied to the normal
931 * argv array at the time they are encountered. That means that if
932 * another string command references a value later in the string it
933 * would fail. We solve that by running FormatString twice. The first
934 * pass makes sure the argv array is correctly filled and the second
935 * pass can reference later values without problems. */
936 struct TextRefStack
*backup
= CreateTextRefStackBackup();
937 FormatString(dry_run_builder
, str_arg
, args
, case_index
, game_script
, true);
938 RestoreTextRefStackBackup(backup
);
940 FormatString(dry_run_builder
, str_arg
, args
, case_index
, game_script
, true);
942 /* We have to restore the original offset here to to read the correct values. */
943 args
.SetOffset(orig_offset
);
946 uint next_substr_case_index
= 0;
947 std::stack
<const char *, std::vector
<const char *>> str_stack
;
948 str_stack
.push(str_arg
);
952 while (!str_stack
.empty() && (b
= Utf8Consume(&str_stack
.top())) == '\0') {
955 if (str_stack
.empty()) break;
956 const char *&str
= str_stack
.top();
958 if (SCC_NEWGRF_FIRST
<= b
&& b
<= SCC_NEWGRF_LAST
) {
959 /* We need to pass some stuff as it might be modified. */
960 StringParameters remaining
= args
.GetRemainingParameters();
961 b
= RemapNewGRFStringControlCode(b
, &str
, remaining
, dry_run
);
962 if (b
== 0) continue;
965 if (b
< SCC_CONTROL_START
|| b
> SCC_CONTROL_END
) {
966 builder
.Utf8Encode(b
);
970 args
.SetTypeOfNextParameter(b
);
973 ArrayStringParameters
<20> sub_args
;
976 uint32_t stringid
= std::strtoul(str
, &p
, 16);
977 if (*p
!= ':' && *p
!= '\0') {
978 while (*p
!= '\0') p
++;
980 builder
+= "(invalid SCC_ENCODED)";
983 if (stringid
>= TAB_SIZE_GAMESCRIPT
) {
984 while (*p
!= '\0') p
++;
986 builder
+= "(invalid StringID)";
991 while (*p
!= '\0' && i
< 20) {
995 /* Find the next value */
996 bool instring
= false;
1003 if (*p
== '"' && escape
) {
1010 instring
= !instring
;
1017 if (*p
== ':') break;
1018 if (*p
== '\0') break;
1022 /* Check if we want to look up another string */
1024 size_t len
= Utf8Decode(&l
, s
);
1025 bool lookup
= (l
== SCC_ENCODED
);
1026 if (lookup
) s
+= len
;
1028 param
= std::strtoull(s
, &p
, 16);
1031 if (param
>= TAB_SIZE_GAMESCRIPT
) {
1032 while (*p
!= '\0') p
++;
1034 builder
+= "(invalid sub-StringID)";
1037 param
= MakeStringID(TEXT_TAB_GAMESCRIPT_START
, param
);
1040 sub_args
.SetParam(i
++, param
);
1042 s
++; // skip the leading \"
1043 sub_args
.SetParam(i
++, std::string(s
, p
- s
- 1)); // also skip the trailing \".
1046 /* If we didn't error out, we can actually print the string. */
1049 GetStringWithArgs(builder
, MakeStringID(TEXT_TAB_GAMESCRIPT_START
, stringid
), sub_args
, true);
1054 case SCC_NEWGRF_STRINL
: {
1055 StringID substr
= Utf8Consume(&str
);
1056 str_stack
.push(GetStringPtr(substr
));
1060 case SCC_NEWGRF_PRINT_WORD_STRING_ID
: {
1061 StringID substr
= args
.GetNextParameter
<StringID
>();
1062 str_stack
.push(GetStringPtr(substr
));
1063 case_index
= next_substr_case_index
;
1064 next_substr_case_index
= 0;
1069 case SCC_GENDER_LIST
: { // {G 0 Der Die Das}
1070 /* First read the meta data from the language file. */
1071 size_t offset
= orig_offset
+ (uint8_t)*str
++;
1073 if (!dry_run
&& args
.GetTypeAtOffset(offset
) != 0) {
1074 /* Now we need to figure out what text to resolve, i.e.
1075 * what do we need to draw? So get the actual raw string
1076 * first using the control code to get said string. */
1078 char *p
= input
+ Utf8Encode(input
, args
.GetTypeAtOffset(offset
));
1081 /* The gender is stored at the start of the formatted string. */
1082 bool old_sgd
= _scan_for_gender_data
;
1083 _scan_for_gender_data
= true;
1085 StringBuilder
tmp_builder(buffer
);
1086 StringParameters tmp_params
= args
.GetRemainingParameters(offset
);
1087 FormatString(tmp_builder
, input
, tmp_params
);
1088 _scan_for_gender_data
= old_sgd
;
1090 /* And determine the string. */
1091 const char *s
= buffer
.c_str();
1092 char32_t c
= Utf8Consume(&s
);
1093 /* Does this string have a gender, if so, set it */
1094 if (c
== SCC_GENDER_INDEX
) gender
= (uint8_t)s
[0];
1096 str
= ParseStringChoice(str
, gender
, builder
);
1100 /* This sets up the gender for the string.
1101 * We just ignore this one. It's used in {G 0 Der Die Das} to determine the case. */
1102 case SCC_GENDER_INDEX
: // {GENDER 0}
1103 if (_scan_for_gender_data
) {
1104 builder
.Utf8Encode(SCC_GENDER_INDEX
);
1111 case SCC_PLURAL_LIST
: { // {P}
1112 int plural_form
= *str
++; // contains the plural form for this string
1113 size_t offset
= orig_offset
+ (uint8_t)*str
++;
1114 const uint64_t *v
= std::get_if
<uint64_t>(&args
.GetParam(offset
)); // contains the number that determines plural
1116 str
= ParseStringChoice(str
, DeterminePluralForm(static_cast<int64_t>(*v
), plural_form
), builder
);
1118 builder
+= "(invalid PLURAL parameter)";
1123 case SCC_ARG_INDEX
: { // Move argument pointer
1124 args
.SetOffset(orig_offset
+ (uint8_t)*str
++);
1128 case SCC_SET_CASE
: { // {SET_CASE}
1129 /* This is a pseudo command, it's outputted when someone does {STRING.ack}
1130 * The modifier is added to all subsequent GetStringWithArgs that accept the modifier. */
1131 next_substr_case_index
= (uint8_t)*str
++;
1135 case SCC_SWITCH_CASE
: { // {Used to implement case switching}
1136 /* <0x9E> <NUM CASES> <CASE1> <LEN1> <STRING1> <CASE2> <LEN2> <STRING2> <CASE3> <LEN3> <STRING3> <STRINGDEFAULT>
1137 * Each LEN is printed using 2 bytes in big endian order. */
1138 uint num
= (uint8_t)*str
++;
1140 if ((uint8_t)str
[0] == case_index
) {
1141 /* Found the case, adjust str pointer and continue */
1145 /* Otherwise skip to the next case */
1146 str
+= 3 + (str
[1] << 8) + str
[2];
1152 case SCC_REVISION
: // {REV}
1153 builder
+= _openttd_revision
;
1156 case SCC_RAW_STRING_POINTER
: { // {RAW_STRING}
1157 const char *raw_string
= args
.GetNextParameterString();
1158 /* raw_string can be nullptr. */
1159 if (raw_string
== nullptr) {
1160 builder
+= "(invalid RAW_STRING parameter)";
1163 FormatString(builder
, raw_string
, args
);
1167 case SCC_STRING
: {// {STRING}
1168 StringID string_id
= args
.GetNextParameter
<StringID
>();
1169 if (game_script
&& GetStringTab(string_id
) != TEXT_TAB_GAMESCRIPT_START
) break;
1170 /* It's prohibited for the included string to consume any arguments. */
1171 StringParameters
tmp_params(args
, game_script
? args
.GetDataLeft() : 0);
1172 GetStringWithArgs(builder
, string_id
, tmp_params
, next_substr_case_index
, game_script
);
1173 next_substr_case_index
= 0;
1183 case SCC_STRING7
: { // {STRING1..7}
1184 /* Strings that consume arguments */
1185 StringID string_id
= args
.GetNextParameter
<StringID
>();
1186 if (game_script
&& GetStringTab(string_id
) != TEXT_TAB_GAMESCRIPT_START
) break;
1187 uint size
= b
- SCC_STRING1
+ 1;
1188 if (game_script
&& size
> args
.GetDataLeft()) {
1189 builder
+= "(too many parameters)";
1191 StringParameters
sub_args(args
, game_script
? args
.GetDataLeft() : size
);
1192 GetStringWithArgs(builder
, string_id
, sub_args
, next_substr_case_index
, game_script
);
1193 args
.AdvanceOffset(size
);
1195 next_substr_case_index
= 0;
1199 case SCC_COMMA
: // {COMMA}
1200 FormatCommaNumber(builder
, args
.GetNextParameter
<int64_t>());
1203 case SCC_DECIMAL
: { // {DECIMAL}
1204 int64_t number
= args
.GetNextParameter
<int64_t>();
1205 int digits
= args
.GetNextParameter
<int>();
1207 FormatCommaNumber(builder
, number
);
1211 int64_t divisor
= PowerOfTen(digits
);
1212 int64_t fractional
= number
% divisor
;
1214 FormatCommaNumber(builder
, number
);
1215 fmt::format_to(builder
, "{}{:0{}d}", GetDecimalSeparator(), fractional
, digits
);
1219 case SCC_NUM
: // {NUM}
1220 FormatNoCommaNumber(builder
, args
.GetNextParameter
<int64_t>());
1223 case SCC_ZEROFILL_NUM
: { // {ZEROFILL_NUM}
1224 int64_t num
= args
.GetNextParameter
<int64_t>();
1225 FormatZerofillNumber(builder
, num
, args
.GetNextParameter
<int>());
1229 case SCC_HEX
: // {HEX}
1230 FormatHexNumber(builder
, args
.GetNextParameter
<uint64_t>());
1233 case SCC_BYTES
: // {BYTES}
1234 FormatBytes(builder
, args
.GetNextParameter
<int64_t>());
1237 case SCC_CARGO_TINY
: { // {CARGO_TINY}
1238 /* Tiny description of cargotypes. Layout:
1239 * param 1: cargo type
1240 * param 2: cargo count */
1241 CargoID cargo
= args
.GetNextParameter
<CargoID
>();
1242 if (cargo
>= CargoSpec::GetArraySize()) break;
1244 StringID cargo_str
= CargoSpec::Get(cargo
)->units_volume
;
1246 switch (cargo_str
) {
1248 amount
= _units_weight
[_settings_game
.locale
.units_weight
].c
.ToDisplay(args
.GetNextParameter
<int64_t>());
1252 amount
= _units_volume
[_settings_game
.locale
.units_volume
].c
.ToDisplay(args
.GetNextParameter
<int64_t>());
1256 amount
= args
.GetNextParameter
<int64_t>();
1261 FormatCommaNumber(builder
, amount
);
1265 case SCC_CARGO_SHORT
: { // {CARGO_SHORT}
1266 /* Short description of cargotypes. Layout:
1267 * param 1: cargo type
1268 * param 2: cargo count */
1269 CargoID cargo
= args
.GetNextParameter
<CargoID
>();
1270 if (cargo
>= CargoSpec::GetArraySize()) break;
1272 StringID cargo_str
= CargoSpec::Get(cargo
)->units_volume
;
1273 switch (cargo_str
) {
1275 assert(_settings_game
.locale
.units_weight
< lengthof(_units_weight
));
1276 const auto &x
= _units_weight
[_settings_game
.locale
.units_weight
];
1277 auto tmp_params
= MakeParameters(x
.c
.ToDisplay(args
.GetNextParameter
<int64_t>()), x
.decimal_places
);
1278 FormatString(builder
, GetStringPtr(x
.l
), tmp_params
);
1283 assert(_settings_game
.locale
.units_volume
< lengthof(_units_volume
));
1284 const auto &x
= _units_volume
[_settings_game
.locale
.units_volume
];
1285 auto tmp_params
= MakeParameters(x
.c
.ToDisplay(args
.GetNextParameter
<int64_t>()), x
.decimal_places
);
1286 FormatString(builder
, GetStringPtr(x
.l
), tmp_params
);
1291 auto tmp_params
= MakeParameters(args
.GetNextParameter
<int64_t>());
1292 GetStringWithArgs(builder
, cargo_str
, tmp_params
);
1299 case SCC_CARGO_LONG
: { // {CARGO_LONG}
1300 /* First parameter is cargo type, second parameter is cargo count */
1301 CargoID cargo
= args
.GetNextParameter
<CargoID
>();
1302 if (IsValidCargoID(cargo
) && cargo
>= CargoSpec::GetArraySize()) break;
1304 StringID cargo_str
= !IsValidCargoID(cargo
) ? STR_QUANTITY_N_A
: CargoSpec::Get(cargo
)->quantifier
;
1305 auto tmp_args
= MakeParameters(args
.GetNextParameter
<int64_t>());
1306 GetStringWithArgs(builder
, cargo_str
, tmp_args
);
1310 case SCC_CARGO_LIST
: { // {CARGO_LIST}
1311 CargoTypes cmask
= args
.GetNextParameter
<CargoTypes
>();
1314 for (const auto &cs
: _sorted_cargo_specs
) {
1315 if (!HasBit(cmask
, cs
->Index())) continue;
1320 /* Add a comma if this is not the first item */
1324 GetStringWithArgs(builder
, cs
->name
, args
, next_substr_case_index
, game_script
);
1327 /* If first is still true then no cargo is accepted */
1328 if (first
) GetStringWithArgs(builder
, STR_JUST_NOTHING
, args
, next_substr_case_index
, game_script
);
1330 next_substr_case_index
= 0;
1334 case SCC_CURRENCY_SHORT
: // {CURRENCY_SHORT}
1335 FormatGenericCurrency(builder
, &GetCurrency(), args
.GetNextParameter
<int64_t>(), true);
1338 case SCC_CURRENCY_LONG
: // {CURRENCY_LONG}
1339 FormatGenericCurrency(builder
, &GetCurrency(), args
.GetNextParameter
<int64_t>(), false);
1342 case SCC_DATE_TINY
: // {DATE_TINY}
1343 FormatTinyOrISODate(builder
, args
.GetNextParameter
<TimerGameCalendar::Date
>(), STR_FORMAT_DATE_TINY
);
1346 case SCC_DATE_SHORT
: // {DATE_SHORT}
1347 FormatMonthAndYear(builder
, args
.GetNextParameter
<TimerGameCalendar::Date
>(), next_substr_case_index
);
1348 next_substr_case_index
= 0;
1351 case SCC_DATE_LONG
: // {DATE_LONG}
1352 FormatYmdString(builder
, args
.GetNextParameter
<TimerGameCalendar::Date
>(), next_substr_case_index
);
1353 next_substr_case_index
= 0;
1356 case SCC_DATE_ISO
: // {DATE_ISO}
1357 FormatTinyOrISODate(builder
, args
.GetNextParameter
<TimerGameCalendar::Date
>(), STR_FORMAT_DATE_ISO
);
1360 case SCC_FORCE
: { // {FORCE}
1361 assert(_settings_game
.locale
.units_force
< lengthof(_units_force
));
1362 const auto &x
= _units_force
[_settings_game
.locale
.units_force
];
1363 auto tmp_params
= MakeParameters(x
.c
.ToDisplay(args
.GetNextParameter
<int64_t>()), x
.decimal_places
);
1364 FormatString(builder
, GetStringPtr(x
.s
), tmp_params
);
1368 case SCC_HEIGHT
: { // {HEIGHT}
1369 assert(_settings_game
.locale
.units_height
< lengthof(_units_height
));
1370 const auto &x
= _units_height
[_settings_game
.locale
.units_height
];
1371 auto tmp_params
= MakeParameters(x
.c
.ToDisplay(args
.GetNextParameter
<int64_t>()), x
.decimal_places
);
1372 FormatString(builder
, GetStringPtr(x
.s
), tmp_params
);
1376 case SCC_POWER
: { // {POWER}
1377 assert(_settings_game
.locale
.units_power
< lengthof(_units_power
));
1378 const auto &x
= _units_power
[_settings_game
.locale
.units_power
];
1379 auto tmp_params
= MakeParameters(x
.c
.ToDisplay(args
.GetNextParameter
<int64_t>()), x
.decimal_places
);
1380 FormatString(builder
, GetStringPtr(x
.s
), tmp_params
);
1384 case SCC_POWER_TO_WEIGHT
: { // {POWER_TO_WEIGHT}
1385 auto setting
= _settings_game
.locale
.units_power
* 3u + _settings_game
.locale
.units_weight
;
1386 assert(setting
< lengthof(_units_power_to_weight
));
1387 const auto &x
= _units_power_to_weight
[setting
];
1388 auto tmp_params
= MakeParameters(x
.c
.ToDisplay(args
.GetNextParameter
<int64_t>()), x
.decimal_places
);
1389 FormatString(builder
, GetStringPtr(x
.s
), tmp_params
);
1393 case SCC_VELOCITY
: { // {VELOCITY}
1394 int64_t arg
= args
.GetNextParameter
<int64_t>();
1395 // Unpack vehicle type from packed argument to get desired units.
1396 VehicleType vt
= static_cast<VehicleType
>(GB(arg
, 56, 8));
1397 const auto &x
= GetVelocityUnits(vt
);
1398 auto tmp_params
= MakeParameters(ConvertKmhishSpeedToDisplaySpeed(GB(arg
, 0, 56), vt
), x
.decimal_places
);
1399 FormatString(builder
, GetStringPtr(x
.s
), tmp_params
);
1403 case SCC_VOLUME_SHORT
: { // {VOLUME_SHORT}
1404 assert(_settings_game
.locale
.units_volume
< lengthof(_units_volume
));
1405 const auto &x
= _units_volume
[_settings_game
.locale
.units_volume
];
1406 auto tmp_params
= MakeParameters(x
.c
.ToDisplay(args
.GetNextParameter
<int64_t>()), x
.decimal_places
);
1407 FormatString(builder
, GetStringPtr(x
.s
), tmp_params
);
1411 case SCC_VOLUME_LONG
: { // {VOLUME_LONG}
1412 assert(_settings_game
.locale
.units_volume
< lengthof(_units_volume
));
1413 const auto &x
= _units_volume
[_settings_game
.locale
.units_volume
];
1414 auto tmp_params
= MakeParameters(x
.c
.ToDisplay(args
.GetNextParameter
<int64_t>()), x
.decimal_places
);
1415 FormatString(builder
, GetStringPtr(x
.l
), tmp_params
);
1419 case SCC_WEIGHT_SHORT
: { // {WEIGHT_SHORT}
1420 assert(_settings_game
.locale
.units_weight
< lengthof(_units_weight
));
1421 const auto &x
= _units_weight
[_settings_game
.locale
.units_weight
];
1422 auto tmp_params
= MakeParameters(x
.c
.ToDisplay(args
.GetNextParameter
<int64_t>()), x
.decimal_places
);
1423 FormatString(builder
, GetStringPtr(x
.s
), tmp_params
);
1427 case SCC_WEIGHT_LONG
: { // {WEIGHT_LONG}
1428 assert(_settings_game
.locale
.units_weight
< lengthof(_units_weight
));
1429 const auto &x
= _units_weight
[_settings_game
.locale
.units_weight
];
1430 auto tmp_params
= MakeParameters(x
.c
.ToDisplay(args
.GetNextParameter
<int64_t>()), x
.decimal_places
);
1431 FormatString(builder
, GetStringPtr(x
.l
), tmp_params
);
1435 case SCC_UNITS_DAYS_OR_SECONDS
: { // {UNITS_DAYS_OR_SECONDS}
1436 uint8_t realtime
= TimerGameEconomy::UsingWallclockUnits(_game_mode
== GM_MENU
);
1437 const auto &x
= _units_time_days_or_seconds
[realtime
];
1438 auto tmp_params
= MakeParameters(x
.c
.ToDisplay(args
.GetNextParameter
<int64_t>()), x
.decimal_places
);
1439 FormatString(builder
, GetStringPtr(x
.s
), tmp_params
);
1443 case SCC_UNITS_MONTHS_OR_MINUTES
: { // {UNITS_MONTHS_OR_MINUTES}
1444 uint8_t realtime
= TimerGameEconomy::UsingWallclockUnits(_game_mode
== GM_MENU
);
1445 const auto &x
= _units_time_months_or_minutes
[realtime
];
1446 auto tmp_params
= MakeParameters(x
.c
.ToDisplay(args
.GetNextParameter
<int64_t>()), x
.decimal_places
);
1447 FormatString(builder
, GetStringPtr(x
.s
), tmp_params
);
1451 case SCC_UNITS_YEARS_OR_PERIODS
: { // {UNITS_YEARS_OR_PERIODS}
1452 uint8_t realtime
= TimerGameEconomy::UsingWallclockUnits(_game_mode
== GM_MENU
);
1453 const auto &x
= _units_time_years_or_periods
[realtime
];
1454 auto tmp_params
= MakeParameters(x
.c
.ToDisplay(args
.GetNextParameter
<int64_t>()), x
.decimal_places
);
1455 FormatString(builder
, GetStringPtr(x
.s
), tmp_params
);
1459 case SCC_UNITS_YEARS_OR_MINUTES
: { // {UNITS_YEARS_OR_MINUTES}
1460 uint8_t realtime
= TimerGameEconomy::UsingWallclockUnits(_game_mode
== GM_MENU
);
1461 const auto &x
= _units_time_years_or_minutes
[realtime
];
1462 auto tmp_params
= MakeParameters(x
.c
.ToDisplay(args
.GetNextParameter
<int64_t>()), x
.decimal_places
);
1463 FormatString(builder
, GetStringPtr(x
.s
), tmp_params
);
1467 case SCC_COMPANY_NAME
: { // {COMPANY}
1468 const Company
*c
= Company::GetIfValid(args
.GetNextParameter
<CompanyID
>());
1469 if (c
== nullptr) break;
1471 if (!c
->name
.empty()) {
1472 auto tmp_params
= MakeParameters(c
->name
);
1473 GetStringWithArgs(builder
, STR_JUST_RAW_STRING
, tmp_params
);
1475 auto tmp_params
= MakeParameters(c
->name_2
);
1476 GetStringWithArgs(builder
, c
->name_1
, tmp_params
);
1481 case SCC_COMPANY_NUM
: { // {COMPANY_NUM}
1482 CompanyID company
= args
.GetNextParameter
<CompanyID
>();
1484 /* Nothing is added for AI or inactive companies */
1485 if (Company::IsValidHumanID(company
)) {
1486 auto tmp_params
= MakeParameters(company
+ 1);
1487 GetStringWithArgs(builder
, STR_FORMAT_COMPANY_NUM
, tmp_params
);
1492 case SCC_DEPOT_NAME
: { // {DEPOT}
1493 VehicleType vt
= args
.GetNextParameter
<VehicleType
>();
1494 if (vt
== VEH_AIRCRAFT
) {
1495 auto tmp_params
= MakeParameters(args
.GetNextParameter
<StationID
>());
1496 GetStringWithArgs(builder
, STR_FORMAT_DEPOT_NAME_AIRCRAFT
, tmp_params
);
1500 const Depot
*d
= Depot::Get(args
.GetNextParameter
<DepotID
>());
1501 if (!d
->name
.empty()) {
1502 auto tmp_params
= MakeParameters(d
->name
);
1503 GetStringWithArgs(builder
, STR_JUST_RAW_STRING
, tmp_params
);
1505 auto tmp_params
= MakeParameters(d
->town
->index
, d
->town_cn
+ 1);
1506 GetStringWithArgs(builder
, STR_FORMAT_DEPOT_NAME_TRAIN
+ 2 * vt
+ (d
->town_cn
== 0 ? 0 : 1), tmp_params
);
1511 case SCC_ENGINE_NAME
: { // {ENGINE}
1512 int64_t arg
= args
.GetNextParameter
<int64_t>();
1513 const Engine
*e
= Engine::GetIfValid(static_cast<EngineID
>(arg
));
1514 if (e
== nullptr) break;
1516 if (!e
->name
.empty() && e
->IsEnabled()) {
1517 auto tmp_params
= MakeParameters(e
->name
);
1518 GetStringWithArgs(builder
, STR_JUST_RAW_STRING
, tmp_params
);
1522 if (HasBit(e
->info
.callback_mask
, CBM_VEHICLE_NAME
)) {
1523 uint16_t callback
= GetVehicleCallback(CBID_VEHICLE_NAME
, static_cast<uint32_t>(arg
>> 32), 0, e
->index
, nullptr);
1524 /* Not calling ErrorUnknownCallbackResult due to being inside string processing. */
1525 if (callback
!= CALLBACK_FAILED
&& callback
< 0x400) {
1526 const GRFFile
*grffile
= e
->GetGRF();
1527 assert(grffile
!= nullptr);
1529 StartTextRefStackUsage(grffile
, 6);
1530 ArrayStringParameters
<6> tmp_params
;
1531 GetStringWithArgs(builder
, GetGRFStringID(grffile
->grfid
, 0xD000 + callback
), tmp_params
);
1532 StopTextRefStackUsage();
1538 auto tmp_params
= ArrayStringParameters
<0>();
1539 GetStringWithArgs(builder
, e
->info
.string_id
, tmp_params
);
1543 case SCC_GROUP_NAME
: { // {GROUP}
1544 const Group
*g
= Group::GetIfValid(args
.GetNextParameter
<GroupID
>());
1545 if (g
== nullptr) break;
1547 if (!g
->name
.empty()) {
1548 auto tmp_params
= MakeParameters(g
->name
);
1549 GetStringWithArgs(builder
, STR_JUST_RAW_STRING
, tmp_params
);
1551 auto tmp_params
= MakeParameters(g
->number
);
1552 GetStringWithArgs(builder
, STR_FORMAT_GROUP_NAME
, tmp_params
);
1557 case SCC_INDUSTRY_NAME
: { // {INDUSTRY}
1558 const Industry
*i
= Industry::GetIfValid(args
.GetNextParameter
<IndustryID
>());
1559 if (i
== nullptr) break;
1561 static bool use_cache
= true;
1562 if (_scan_for_gender_data
) {
1563 /* Gender is defined by the industry type.
1564 * STR_FORMAT_INDUSTRY_NAME may have the town first, so it would result in the gender of the town name */
1565 auto tmp_params
= ArrayStringParameters
<0>();
1566 FormatString(builder
, GetStringPtr(GetIndustrySpec(i
->type
)->name
), tmp_params
, next_substr_case_index
);
1567 } else if (use_cache
) { // Use cached version if first call
1568 AutoRestoreBackup
cache_backup(use_cache
, false);
1569 builder
+= i
->GetCachedName();
1571 /* First print the town name and the industry type name. */
1572 auto tmp_params
= MakeParameters(i
->town
->index
, GetIndustrySpec(i
->type
)->name
);
1573 FormatString(builder
, GetStringPtr(STR_FORMAT_INDUSTRY_NAME
), tmp_params
, next_substr_case_index
);
1575 next_substr_case_index
= 0;
1579 case SCC_PRESIDENT_NAME
: { // {PRESIDENT_NAME}
1580 const Company
*c
= Company::GetIfValid(args
.GetNextParameter
<CompanyID
>());
1581 if (c
== nullptr) break;
1583 if (!c
->president_name
.empty()) {
1584 auto tmp_params
= MakeParameters(c
->president_name
);
1585 GetStringWithArgs(builder
, STR_JUST_RAW_STRING
, tmp_params
);
1587 auto tmp_params
= MakeParameters(c
->president_name_2
);
1588 GetStringWithArgs(builder
, c
->president_name_1
, tmp_params
);
1593 case SCC_STATION_NAME
: { // {STATION}
1594 StationID sid
= args
.GetNextParameter
<StationID
>();
1595 const Station
*st
= Station::GetIfValid(sid
);
1597 if (st
== nullptr) {
1598 /* The station doesn't exist anymore. The only place where we might
1599 * be "drawing" an invalid station is in the case of cargo that is
1601 auto tmp_params
= ArrayStringParameters
<0>();
1602 GetStringWithArgs(builder
, STR_UNKNOWN_STATION
, tmp_params
);
1606 static bool use_cache
= true;
1607 if (use_cache
) { // Use cached version if first call
1608 AutoRestoreBackup
cache_backup(use_cache
, false);
1609 builder
+= st
->GetCachedName();
1610 } else if (!st
->name
.empty()) {
1611 auto tmp_params
= MakeParameters(st
->name
);
1612 GetStringWithArgs(builder
, STR_JUST_RAW_STRING
, tmp_params
);
1614 StringID string_id
= st
->string_id
;
1615 if (st
->indtype
!= IT_INVALID
) {
1616 /* Special case where the industry provides the name for the station */
1617 const IndustrySpec
*indsp
= GetIndustrySpec(st
->indtype
);
1619 /* Industry GRFs can change which might remove the station name and
1620 * thus cause very strange things. Here we check for that before we
1621 * actually set the station name. */
1622 if (indsp
->station_name
!= STR_NULL
&& indsp
->station_name
!= STR_UNDEFINED
) {
1623 string_id
= indsp
->station_name
;
1627 auto tmp_params
= MakeParameters(STR_TOWN_NAME
, st
->town
->index
, st
->index
);
1628 GetStringWithArgs(builder
, string_id
, tmp_params
);
1633 case SCC_TOWN_NAME
: { // {TOWN}
1634 const Town
*t
= Town::GetIfValid(args
.GetNextParameter
<TownID
>());
1635 if (t
== nullptr) break;
1637 static bool use_cache
= true;
1638 if (use_cache
) { // Use cached version if first call
1639 AutoRestoreBackup
cache_backup(use_cache
, false);
1640 builder
+= t
->GetCachedName();
1641 } else if (!t
->name
.empty()) {
1642 auto tmp_params
= MakeParameters(t
->name
);
1643 GetStringWithArgs(builder
, STR_JUST_RAW_STRING
, tmp_params
);
1645 GetTownName(builder
, t
);
1650 case SCC_WAYPOINT_NAME
: { // {WAYPOINT}
1651 Waypoint
*wp
= Waypoint::GetIfValid(args
.GetNextParameter
<StationID
>());
1652 if (wp
== nullptr) break;
1654 if (!wp
->name
.empty()) {
1655 auto tmp_params
= MakeParameters(wp
->name
);
1656 GetStringWithArgs(builder
, STR_JUST_RAW_STRING
, tmp_params
);
1658 auto tmp_params
= MakeParameters(wp
->town
->index
, wp
->town_cn
+ 1);
1659 StringID string_id
= ((wp
->string_id
== STR_SV_STNAME_BUOY
) ? STR_FORMAT_BUOY_NAME
: STR_FORMAT_WAYPOINT_NAME
);
1660 if (wp
->town_cn
!= 0) string_id
++;
1661 GetStringWithArgs(builder
, string_id
, tmp_params
);
1666 case SCC_VEHICLE_NAME
: { // {VEHICLE}
1667 const Vehicle
*v
= Vehicle::GetIfValid(args
.GetNextParameter
<VehicleID
>());
1668 if (v
== nullptr) break;
1670 if (!v
->name
.empty()) {
1671 auto tmp_params
= MakeParameters(v
->name
);
1672 GetStringWithArgs(builder
, STR_JUST_RAW_STRING
, tmp_params
);
1673 } else if (v
->group_id
!= DEFAULT_GROUP
) {
1674 /* The vehicle has no name, but is member of a group, so print group name */
1675 auto tmp_params
= MakeParameters(v
->group_id
, v
->unitnumber
);
1676 GetStringWithArgs(builder
, STR_FORMAT_GROUP_VEHICLE_NAME
, tmp_params
);
1678 auto tmp_params
= MakeParameters(v
->unitnumber
);
1682 default: string_id
= STR_INVALID_VEHICLE
; break;
1683 case VEH_TRAIN
: string_id
= STR_SV_TRAIN_NAME
; break;
1684 case VEH_ROAD
: string_id
= STR_SV_ROAD_VEHICLE_NAME
; break;
1685 case VEH_SHIP
: string_id
= STR_SV_SHIP_NAME
; break;
1686 case VEH_AIRCRAFT
: string_id
= STR_SV_AIRCRAFT_NAME
; break;
1689 GetStringWithArgs(builder
, string_id
, tmp_params
);
1694 case SCC_SIGN_NAME
: { // {SIGN}
1695 const Sign
*si
= Sign::GetIfValid(args
.GetNextParameter
<SignID
>());
1696 if (si
== nullptr) break;
1698 if (!si
->name
.empty()) {
1699 auto tmp_params
= MakeParameters(si
->name
);
1700 GetStringWithArgs(builder
, STR_JUST_RAW_STRING
, tmp_params
);
1702 auto tmp_params
= ArrayStringParameters
<0>();
1703 GetStringWithArgs(builder
, STR_DEFAULT_SIGN_NAME
, tmp_params
);
1708 case SCC_STATION_FEATURES
: { // {STATIONFEATURES}
1709 StationGetSpecialString(builder
, args
.GetNextParameter
<StationFacility
>());
1713 case SCC_COLOUR
: { // {COLOUR}
1714 StringControlCode scc
= (StringControlCode
)(SCC_BLUE
+ args
.GetNextParameter
<Colours
>());
1715 if (IsInsideMM(scc
, SCC_BLUE
, SCC_COLOUR
)) builder
.Utf8Encode(scc
);
1720 builder
.Utf8Encode(b
);
1723 } catch (std::out_of_range
&e
) {
1724 Debug(misc
, 0, "FormatString: {}", e
.what());
1725 builder
+= "(invalid parameter)";
1731 static void StationGetSpecialString(StringBuilder
&builder
, StationFacility x
)
1733 if ((x
& FACIL_TRAIN
) != 0) builder
.Utf8Encode(SCC_TRAIN
);
1734 if ((x
& FACIL_TRUCK_STOP
) != 0) builder
.Utf8Encode(SCC_LORRY
);
1735 if ((x
& FACIL_BUS_STOP
) != 0) builder
.Utf8Encode(SCC_BUS
);
1736 if ((x
& FACIL_DOCK
) != 0) builder
.Utf8Encode(SCC_SHIP
);
1737 if ((x
& FACIL_AIRPORT
) != 0) builder
.Utf8Encode(SCC_PLANE
);
1740 static void GetSpecialTownNameString(StringBuilder
&builder
, int ind
, uint32_t seed
)
1742 GenerateTownNameString(builder
, ind
, seed
);
1745 static const char * const _silly_company_names
[] = {
1747 "Tiny Transport Ltd.",
1749 "Comfy-Coach & Co.",
1750 "Crush & Bump Ltd.",
1751 "Broken & Late Ltd.",
1753 "Supersonic Travel",
1755 "Lightning International",
1756 "Pannik & Loozit Ltd.",
1757 "Inter-City Transport",
1758 "Getout & Pushit Ltd."
1761 static const char * const _surname_list
[] = {
1793 static const char * const _silly_surname_list
[] = {
1808 static const char _initial_name_letters
[] = {
1809 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J',
1810 'K', 'L', 'M', 'N', 'P', 'R', 'S', 'T', 'W',
1813 static std::span
<const char * const> GetSurnameOptions()
1815 if (_settings_game
.game_creation
.landscape
== LT_TOYLAND
) return _silly_surname_list
;
1816 return _surname_list
;
1820 * Get the surname of the president with the given seed.
1821 * @param seed The seed the surname was generated from.
1822 * @return The surname.
1824 static const char *GetSurname(uint32_t seed
)
1826 auto surname_options
= GetSurnameOptions();
1827 return surname_options
[surname_options
.size() * GB(seed
, 16, 8) >> 8];
1830 static void GenAndCoName(StringBuilder
&builder
, uint32_t seed
)
1832 builder
+= GetSurname(seed
);
1833 builder
+= " & Co.";
1836 static void GenPresidentName(StringBuilder
&builder
, uint32_t seed
)
1838 builder
+= _initial_name_letters
[std::size(_initial_name_letters
) * GB(seed
, 0, 8) >> 8];
1841 /* The second initial is optional. */
1842 size_t index
= (std::size(_initial_name_letters
) + 35) * GB(seed
, 8, 8) >> 8;
1843 if (index
< std::size(_initial_name_letters
)) {
1844 builder
+= _initial_name_letters
[index
];
1848 builder
+= GetSurname(seed
);
1851 static void GetSpecialNameString(StringBuilder
&builder
, int ind
, StringParameters
&args
)
1855 builder
+= _silly_company_names
[std::min
<size_t>(args
.GetNextParameter
<uint16_t>(), std::size(_silly_company_names
) - 1)];
1858 case 2: // used for Foobar & Co company names
1859 GenAndCoName(builder
, args
.GetNextParameter
<uint32_t>());
1862 case 3: // President name
1863 GenPresidentName(builder
, args
.GetNextParameter
<uint32_t>());
1868 if (IsInsideMM(ind
- 6, 0, SPECSTR_TOWNNAME_LAST
- SPECSTR_TOWNNAME_START
+ 1)) {
1869 GetSpecialTownNameString(builder
, ind
- 6, args
.GetNextParameter
<uint32_t>());
1870 builder
+= " Transport";
1878 * Check whether the header is a valid header for OpenTTD.
1879 * @return true iff the header is deemed valid.
1881 bool LanguagePackHeader::IsValid() const
1883 return this->ident
== TO_LE32(LanguagePackHeader::IDENT
) &&
1884 this->version
== TO_LE32(LANGUAGE_PACK_VERSION
) &&
1885 this->plural_form
< LANGUAGE_MAX_PLURAL
&&
1886 this->text_dir
<= 1 &&
1887 this->newgrflangid
< MAX_LANG
&&
1888 this->num_genders
< MAX_NUM_GENDERS
&&
1889 this->num_cases
< MAX_NUM_CASES
&&
1890 StrValid(this->name
) &&
1891 StrValid(this->own_name
) &&
1892 StrValid(this->isocode
) &&
1893 StrValid(this->digit_group_separator
) &&
1894 StrValid(this->digit_group_separator_currency
) &&
1895 StrValid(this->digit_decimal_separator
);
1899 * Check whether a translation is sufficiently finished to offer it to the public.
1901 bool LanguagePackHeader::IsReasonablyFinished() const
1903 /* "Less than 25% missing" is "sufficiently finished". */
1904 return 4 * this->missing
< LANGUAGE_TOTAL_STRINGS
;
1908 * Read a particular language.
1909 * @param lang The metadata about the language.
1910 * @return Whether the loading went okay or not.
1912 bool ReadLanguagePack(const LanguageMetadata
*lang
)
1914 /* Current language pack */
1916 std::unique_ptr
<LanguagePack
, LanguagePackDeleter
> lang_pack(reinterpret_cast<LanguagePack
*>(ReadFileToMem(FS2OTTD(lang
->file
), len
, 1U << 20).release()));
1917 if (!lang_pack
) return false;
1919 /* End of read data (+ terminating zero added in ReadFileToMem()) */
1920 const char *end
= (char *)lang_pack
.get() + len
+ 1;
1922 /* We need at least one byte of lang_pack->data */
1923 if (end
<= lang_pack
->data
|| !lang_pack
->IsValid()) {
1927 std::array
<uint
, TEXT_TAB_END
> tab_start
, tab_num
;
1930 for (uint i
= 0; i
< TEXT_TAB_END
; i
++) {
1931 uint16_t num
= FROM_LE16(lang_pack
->offsets
[i
]);
1932 if (num
> TAB_SIZE
) return false;
1934 tab_start
[i
] = count
;
1939 /* Allocate offsets */
1940 std::vector
<char *> offs(count
);
1943 char *s
= lang_pack
->data
;
1944 len
= (uint8_t)*s
++;
1945 for (uint i
= 0; i
< count
; i
++) {
1946 if (s
+ len
>= end
) return false;
1949 len
= ((len
& 0x3F) << 8) + (uint8_t)*s
++;
1950 if (s
+ len
>= end
) return false;
1955 *s
++ = '\0'; // zero terminate the string
1958 _langpack
.langpack
= std::move(lang_pack
);
1959 _langpack
.offsets
= std::move(offs
);
1960 _langpack
.langtab_num
= tab_num
;
1961 _langpack
.langtab_start
= tab_start
;
1963 _current_language
= lang
;
1964 _current_text_dir
= (TextDirection
)_current_language
->text_dir
;
1965 _config_language_file
= FS2OTTD(_current_language
->file
.filename());
1966 SetCurrentGrfLangID(_current_language
->newgrflangid
);
1969 extern void Win32SetCurrentLocaleName(std::string iso_code
);
1970 Win32SetCurrentLocaleName(_current_language
->isocode
);
1974 extern void MacOSSetCurrentLocaleName(const char *iso_code
);
1975 MacOSSetCurrentLocaleName(_current_language
->isocode
);
1978 #ifdef WITH_ICU_I18N
1979 /* Create a collator instance for our current locale. */
1980 UErrorCode status
= U_ZERO_ERROR
;
1981 _current_collator
.reset(icu::Collator::createInstance(icu::Locale(_current_language
->isocode
), status
));
1982 /* Sort number substrings by their numerical value. */
1983 if (_current_collator
) _current_collator
->setAttribute(UCOL_NUMERIC_COLLATION
, UCOL_ON
, status
);
1984 /* Avoid using the collator if it is not correctly set. */
1985 if (U_FAILURE(status
)) {
1986 _current_collator
.reset();
1988 #endif /* WITH_ICU_I18N */
1990 Layouter::Initialize();
1992 /* Some lists need to be sorted again after a language change. */
1993 ReconsiderGameScriptLanguage();
1994 InitializeSortedCargoSpecs();
1995 SortIndustryTypes();
1996 BuildIndustriesLegend();
1997 BuildContentTypeStringList();
1998 InvalidateWindowClassesData(WC_BUILD_VEHICLE
); // Build vehicle window.
1999 InvalidateWindowClassesData(WC_TRAINS_LIST
); // Train group window.
2000 InvalidateWindowClassesData(WC_ROADVEH_LIST
); // Road vehicle group window.
2001 InvalidateWindowClassesData(WC_SHIPS_LIST
); // Ship group window.
2002 InvalidateWindowClassesData(WC_AIRCRAFT_LIST
); // Aircraft group window.
2003 InvalidateWindowClassesData(WC_INDUSTRY_DIRECTORY
); // Industry directory window.
2004 InvalidateWindowClassesData(WC_STATION_LIST
); // Station list window.
2009 /* Win32 implementation in win32.cpp.
2010 * OS X implementation in os/macosx/macos.mm. */
2011 #if !(defined(_WIN32) || defined(__APPLE__))
2013 * Determine the current charset based on the environment
2014 * First check some default values, after this one we passed ourselves
2015 * and if none exist return the value for $LANG
2016 * @param param environment variable to check conditionally if default ones are not
2017 * set. Pass nullptr if you don't want additional checks.
2018 * @return return string containing current charset, or nullptr if not-determinable
2020 const char *GetCurrentLocale(const char *param
)
2024 env
= std::getenv("LANGUAGE");
2025 if (env
!= nullptr) return env
;
2027 env
= std::getenv("LC_ALL");
2028 if (env
!= nullptr) return env
;
2030 if (param
!= nullptr) {
2031 env
= std::getenv(param
);
2032 if (env
!= nullptr) return env
;
2035 return std::getenv("LANG");
2038 const char *GetCurrentLocale(const char *param
);
2039 #endif /* !(defined(_WIN32) || defined(__APPLE__)) */
2042 * Get the language with the given NewGRF language ID.
2043 * @param newgrflangid NewGRF languages ID to check.
2044 * @return The language's metadata, or nullptr if it is not known.
2046 const LanguageMetadata
*GetLanguage(uint8_t newgrflangid
)
2048 for (const LanguageMetadata
&lang
: _languages
) {
2049 if (newgrflangid
== lang
.newgrflangid
) return &lang
;
2056 * Reads the language file header and checks compatibility.
2057 * @param file the file to read
2058 * @param hdr the place to write the header information to
2059 * @return true if and only if the language file is of a compatible version
2061 static bool GetLanguageFileHeader(const std::string
&file
, LanguagePackHeader
*hdr
)
2063 auto f
= FileHandle::Open(file
, "rb");
2064 if (!f
.has_value()) return false;
2066 size_t read
= fread(hdr
, sizeof(*hdr
), 1, *f
);
2068 bool ret
= read
== 1 && hdr
->IsValid();
2070 /* Convert endianness for the windows language ID */
2072 hdr
->missing
= FROM_LE16(hdr
->missing
);
2073 hdr
->winlangid
= FROM_LE16(hdr
->winlangid
);
2079 * Search for the languages in the given directory and add them to the #_languages list.
2080 * @param path the base directory to search in
2082 static void FillLanguageList(const std::string
&path
)
2084 std::error_code error_code
;
2085 for (const auto &dir_entry
: std::filesystem::directory_iterator(OTTD2FS(path
), error_code
)) {
2086 if (!dir_entry
.is_regular_file()) continue;
2087 if (dir_entry
.path().extension() != ".lng") continue;
2089 LanguageMetadata lmd
;
2090 lmd
.file
= dir_entry
.path();
2092 /* Check whether the file is of the correct version */
2093 if (!GetLanguageFileHeader(FS2OTTD(lmd
.file
), &lmd
)) {
2094 Debug(misc
, 3, "{} is not a valid language file", FS2OTTD(lmd
.file
));
2095 } else if (GetLanguage(lmd
.newgrflangid
) != nullptr) {
2096 Debug(misc
, 3, "{}'s language ID is already known", FS2OTTD(lmd
.file
));
2098 _languages
.push_back(lmd
);
2102 Debug(misc
, 9, "Unable to open directory {}: {}", path
, error_code
.message());
2107 * Make a list of the available language packs. Put the data in
2110 void InitializeLanguagePacks()
2112 for (Searchpath sp
: _valid_searchpaths
) {
2113 FillLanguageList(FioGetDirectory(sp
, LANG_DIR
));
2115 if (_languages
.empty()) UserError("No available language packs (invalid versions?)");
2117 /* Acquire the locale of the current system */
2118 const char *lang
= GetCurrentLocale("LC_MESSAGES");
2119 if (lang
== nullptr) lang
= "en_GB";
2121 const LanguageMetadata
*chosen_language
= nullptr; ///< Matching the language in the configuration file or the current locale
2122 const LanguageMetadata
*language_fallback
= nullptr; ///< Using pt_PT for pt_BR locale when pt_BR is not available
2123 const LanguageMetadata
*en_GB_fallback
= _languages
.data(); ///< Fallback when no locale-matching language has been found
2125 /* Find a proper language. */
2126 for (const LanguageMetadata
&lng
: _languages
) {
2127 /* We are trying to find a default language. The priority is by
2128 * configuration file, local environment and last, if nothing found,
2130 if (_config_language_file
== FS2OTTD(lng
.file
.filename())) {
2131 chosen_language
= &lng
;
2135 if (strcmp (lng
.isocode
, "en_GB") == 0) en_GB_fallback
= &lng
;
2137 /* Only auto-pick finished translations */
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
;
2144 /* We haven't found the language in the config nor the one in the locale.
2145 * Now we set it to one of the fallback languages */
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 '{}'", FS2OTTD(chosen_language
->file
));
2154 * Get the ISO language code of the currently loaded language.
2155 * @return the ISO code.
2157 const char *GetCurrentLanguageIsoCode()
2159 return _langpack
.langpack
->isocode
;
2163 * Check whether there are glyphs missing in the current language.
2164 * @return If glyphs are missing, return \c true, else return \c false.
2166 bool MissingGlyphSearcher::FindMissingGlyphs()
2168 InitFontCache(this->Monospace());
2171 for (auto text
= this->NextString(); text
.has_value(); text
= this->NextString()) {
2172 auto src
= text
->cbegin();
2174 FontSize size
= this->DefaultSize();
2175 FontCache
*fc
= FontCache::Get(size
);
2176 while (src
!= text
->cend()) {
2177 char32_t c
= Utf8Consume(src
);
2179 if (c
>= SCC_FIRST_FONT
&& c
<= SCC_LAST_FONT
) {
2180 size
= (FontSize
)(c
- SCC_FIRST_FONT
);
2181 fc
= FontCache::Get(size
);
2182 } else if (!IsInsideMM(c
, SCC_SPRITE_START
, SCC_SPRITE_END
) && IsPrintable(c
) && !IsTextDirectionChar(c
) && fc
->MapCharToGlyph(c
, false) == 0) {
2183 /* The character is printable, but not in the normal font. This is the case we were testing for. */
2184 std::string size_name
;
2187 case FS_NORMAL
: size_name
= "medium"; break;
2188 case FS_SMALL
: size_name
= "small"; break;
2189 case FS_LARGE
: size_name
= "large"; break;
2190 case FS_MONO
: size_name
= "mono"; break;
2191 default: NOT_REACHED();
2194 Debug(fontcache
, 0, "Font is missing glyphs to display char 0x{:X} in {} font size", (int)c
, size_name
);
2202 /** Helper for searching through the language pack. */
2203 class LanguagePackGlyphSearcher
: public MissingGlyphSearcher
{
2204 uint i
; ///< Iterator for the primary language tables.
2205 uint j
; ///< Iterator for the secondary language tables.
2207 void Reset() override
2213 FontSize
DefaultSize() override
2218 std::optional
<std::string_view
> NextString() override
2220 if (this->i
>= TEXT_TAB_END
) return std::nullopt
;
2222 const char *ret
= _langpack
.offsets
[_langpack
.langtab_start
[this->i
] + this->j
];
2225 while (this->i
< TEXT_TAB_END
&& this->j
>= _langpack
.langtab_num
[this->i
]) {
2233 bool Monospace() override
2238 void SetFontNames([[maybe_unused
]] FontCacheSettings
*settings
, [[maybe_unused
]] const char *font_name
, [[maybe_unused
]] const void *os_data
) override
2240 #if defined(WITH_FREETYPE) || defined(_WIN32) || defined(WITH_COCOA)
2241 settings
->small
.font
= font_name
;
2242 settings
->medium
.font
= font_name
;
2243 settings
->large
.font
= font_name
;
2245 settings
->small
.os_handle
= os_data
;
2246 settings
->medium
.os_handle
= os_data
;
2247 settings
->large
.os_handle
= os_data
;
2253 * Check whether the currently loaded language pack
2254 * uses characters that the currently loaded font
2255 * does not support. If this is the case an error
2256 * message will be shown in English. The error
2257 * message will not be localized because that would
2258 * mean it might use characters that are not in the
2259 * font, which is the whole reason this check has
2261 * @param base_font Whether to look at the base font as well.
2262 * @param searcher The methods to use to search for strings to check.
2263 * If nullptr the loaded language pack searcher is used.
2265 void CheckForMissingGlyphs(bool base_font
, MissingGlyphSearcher
*searcher
)
2267 static LanguagePackGlyphSearcher pack_searcher
;
2268 if (searcher
== nullptr) searcher
= &pack_searcher
;
2269 bool bad_font
= !base_font
|| searcher
->FindMissingGlyphs();
2270 #if defined(WITH_FREETYPE) || defined(_WIN32) || defined(WITH_COCOA)
2272 /* We found an unprintable character... lets try whether we can find
2273 * a fallback font that can print the characters in the current language. */
2274 bool any_font_configured
= !_fcsettings
.medium
.font
.empty();
2275 FontCacheSettings backup
= _fcsettings
;
2277 _fcsettings
.mono
.os_handle
= nullptr;
2278 _fcsettings
.medium
.os_handle
= nullptr;
2280 bad_font
= !SetFallbackFont(&_fcsettings
, _langpack
.langpack
->isocode
, _langpack
.langpack
->winlangid
, searcher
);
2282 _fcsettings
= backup
;
2284 if (!bad_font
&& any_font_configured
) {
2285 /* If the user configured a bad font, and we found a better one,
2286 * show that we loaded the better font instead of the configured one.
2287 * The colour 'character' might change in the
2288 * future, so for safety we just Utf8 Encode it into the string,
2289 * which takes exactly three characters, so it replaces the "XXX"
2290 * with the colour marker. */
2291 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.");
2292 Utf8Encode(err_str
.data(), SCC_YELLOW
);
2293 SetDParamStr(0, err_str
);
2294 ShowErrorMessage(STR_JUST_RAW_STRING
, INVALID_STRING_ID
, WL_WARNING
);
2297 if (bad_font
&& base_font
) {
2298 /* Our fallback font does miss characters too, so keep the
2299 * user chosen font as that is more likely to be any good than
2300 * the wild guess we made */
2301 InitFontCache(searcher
->Monospace());
2307 /* All attempts have failed. Display an error. As we do not want the string to be translated by
2308 * the translators, we 'force' it into the binary and 'load' it via a BindCString. To do this
2309 * properly we have to set the colour of the string, otherwise we end up with a lot of artifacts.
2310 * The colour 'character' might change in the future, so for safety we just Utf8 Encode it into
2311 * the string, which takes exactly three characters, so it replaces the "XXX" with the colour marker. */
2312 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.");
2313 Utf8Encode(err_str
.data(), SCC_YELLOW
);
2314 SetDParamStr(0, err_str
);
2315 ShowErrorMessage(STR_JUST_RAW_STRING
, INVALID_STRING_ID
, WL_WARNING
);
2317 /* Reset the font width */
2318 LoadStringWidthTable(searcher
->Monospace());
2322 /* Update the font with cache */
2323 LoadStringWidthTable(searcher
->Monospace());
2325 #if !(defined(WITH_ICU_I18N) && defined(WITH_HARFBUZZ)) && !defined(WITH_UNISCRIBE) && !defined(WITH_COCOA)
2327 * For right-to-left languages we need the ICU library. If
2328 * we do not have support for that library we warn the user
2329 * about it with a message. As we do not want the string to
2330 * be translated by the translators, we 'force' it into the
2331 * binary and 'load' it via a BindCString. To do this
2332 * properly we have to set the colour of the string,
2333 * otherwise we end up with a lot of artifacts. The colour
2334 * 'character' might change in the future, so for safety
2335 * we just Utf8 Encode it into the string, which takes
2336 * exactly three characters, so it replaces the "XXX" with
2337 * the colour marker.
2339 if (_current_text_dir
!= TD_LTR
) {
2340 static std::string
err_str("XXXThis version of OpenTTD does not support right-to-left languages. Recompile with ICU + Harfbuzz enabled.");
2341 Utf8Encode(err_str
.data(), SCC_YELLOW
);
2342 SetDParamStr(0, err_str
);
2343 ShowErrorMessage(STR_JUST_RAW_STRING
, INVALID_STRING_ID
, WL_ERROR
);
2345 #endif /* !(WITH_ICU_I18N && WITH_HARFBUZZ) && !WITH_UNISCRIBE && !WITH_COCOA */