diff --git a/data/mods/TEST_DATA/lang/po/ru.po b/data/mods/TEST_DATA/lang/po/ru.po index 5b6acd57cfccf..5bd50ce086be4 100644 --- a/data/mods/TEST_DATA/lang/po/ru.po +++ b/data/mods/TEST_DATA/lang/po/ru.po @@ -39,3 +39,10 @@ msgstr[0] "щука" msgstr[1] "щуки" msgstr[2] "щук" msgstr[3] "щуки" + +msgid "tname_segments_order" +msgstr "FAULTS DIRT OVERHEAT FAVORITE_PRE DURABILITY WHEEL_DIAMETER WEAPON_MODS CUSTOM_ITEM_PREFIX TYPE BURN" + +msgctxt "burnt adjective" +msgid "burnt " +msgstr " (burnt)" diff --git a/src/item.cpp b/src/item.cpp index 9b791a9a7e605..9ffb8b93ebe8e 100644 --- a/src/item.cpp +++ b/src/item.cpp @@ -6859,8 +6859,7 @@ std::string item::tname( unsigned int quantity, tname::segment_bitset const &seg { std::string ret; - for( size_t i = 0; i < static_cast( tname::segments::last_segment ); i++ ) { - tname::segments const idx = static_cast( i ); + for( tname::segments idx : tname::get_tname_set() ) { if( !segments[idx] ) { continue; } diff --git a/src/item_tname.cpp b/src/item_tname.cpp index 5b4692fba670a..c4956e331696c 100644 --- a/src/item_tname.cpp +++ b/src/item_tname.cpp @@ -609,13 +609,153 @@ static_assert( all_segments_have_printers(), "every element of tname::segments (up to tname::segments::last_segment) " "must map to a printer in segs_array" ); + +namespace io +{ +template<> +std::string enum_to_string( tname::segments seg ) +{ + switch( seg ) { + // *INDENT-OFF* + case tname::segments::FAULTS: return "FAULTS"; + case tname::segments::DIRT: return "DIRT"; + case tname::segments::OVERHEAT: return "OVERHEAT"; + case tname::segments::FAVORITE_PRE: return "FAVORITE_PRE"; + case tname::segments::DURABILITY: return "DURABILITY"; + case tname::segments::WHEEL_DIAMETER: return "WHEEL_DIAMETER"; + case tname::segments::BURN: return "BURN"; + case tname::segments::WEAPON_MODS: return "WEAPON_MODS"; + case tname::segments::CUSTOM_ITEM_PREFIX: return "CUSTOM_ITEM_PREFIX"; + case tname::segments::TYPE: return "TYPE"; + case tname::segments::CATEGORY: return "CATEGORY"; + case tname::segments::CUSTOM_ITEM_SUFFIX: return "CUSTOM_ITEM_SUFFIX"; + case tname::segments::MODS: return "MODS"; + case tname::segments::CRAFT: return "CRAFT"; + case tname::segments::WHITEBLACKLIST: return "WHITEBLACKLIST"; + case tname::segments::CHARGES: return "CHARGES"; + case tname::segments::FOOD_TRAITS: return "FOOD_TRAITS"; + case tname::segments::FOOD_STATUS: return "FOOD_STATUS"; + case tname::segments::FOOD_IRRADIATED: return "FOOD_IRRADIATED"; + case tname::segments::TEMPERATURE: return "TEMPERATURE"; + case tname::segments::LOCATION_HINT: return "LOCATION_HINT"; + case tname::segments::CLOTHING_SIZE: return "CLOTHING_SIZE"; + case tname::segments::ETHEREAL: return "ETHEREAL"; + case tname::segments::FILTHY: return "FILTHY"; + case tname::segments::BROKEN: return "BROKEN"; + case tname::segments::CBM_STATUS: return "CBM_STATUS"; + case tname::segments::UPS: return "UPS"; + case tname::segments::TAGS: return "TAGS"; + case tname::segments::VARS: return "VARS"; + case tname::segments::WETNESS: return "WETNESS"; + case tname::segments::ACTIVE: return "ACTIVE"; + case tname::segments::SEALED: return "SEALED"; + case tname::segments::FAVORITE_POST: return "FAVORITE_POST"; + case tname::segments::RELIC: return "RELIC"; + case tname::segments::LINK: return "LINK"; + case tname::segments::TECHNIQUES: return "TECHNIQUES"; + case tname::segments::CONTENTS: return "CONTENTS"; + case tname::segments::last_segment: return "last_segment"; + case tname::segments::VARIANT: return "VARIANT"; + case tname::segments::COMPONENTS: return "COMPONENTS"; + case tname::segments::CORPSE: return "CORPSE"; + case tname::segments::CONTENTS_FULL: return "CONTENTS_FULL"; + case tname::segments::CONTENTS_ABREV: return "CONTENTS_ABBREV"; + case tname::segments::CONTENTS_COUNT: return "CONTENTS_COUNT"; + case tname::segments::FOOD_PERISHABLE: return "FOOD_PERISHABLE"; + case tname::segments::last: return "last"; + default: + // *INDENT-ON* + break; + } + return {}; +} + +} // namespace io + +namespace +{ + +constexpr tname::segments fixed_pos_segments = tname::segments::CONTENTS; +static_assert( fixed_pos_segments <= tname::segments::last_segment ); + +using tname_array = std::array( fixed_pos_segments )>; +struct segment_order { + constexpr explicit segment_order( tname_array const &arr_ ) : arr( &arr_ ) {}; + constexpr bool operator()( tname::segments lhs, tname::segments rhs ) const { + return arr->at( static_cast( lhs ) ) < + arr->at( static_cast( rhs ) ); + } + + tname_array const *arr; +}; + +std::optional str_to_segment_idx( std::string const &str ) +{ + if( std::optional ret = io::string_to_enum_optional( str ); + ret && ret < fixed_pos_segments ) { + + return static_cast( *ret ); + } + + return {}; +} + +} // namespace namespace tname { std::string print_segment( tname::segments segment, item const &it, unsigned int quantity, segment_bitset const &segments ) { static std::array const arr = get_segs_array(); - size_t const idx = static_cast( segment ); + std::size_t const idx = static_cast( segment ); return ( *arr.at( idx ) )( it, quantity, segments ); } + +tname_set const &get_tname_set() +{ + static tname_set tns; + static int lang_ver = INVALID_LANGUAGE_VERSION; + if( int const cur_lang_ver = detail::get_current_language_version(); lang_ver != cur_lang_ver ) { + lang_ver = cur_lang_ver; + tns.clear(); + for( std::size_t i = 0; i < static_cast( fixed_pos_segments ); i++ ) { + tns.emplace_back( static_cast( i ) ); + } + + //~ You can use this string to change the order of item name segments. The default order is: + //~ FAULTS DIRT OVERHEAT FAVORITE_PRE DURABILITY WHEEL_DIAMETER BURN WEAPON_MODS + //~ CUSTOM_ITEM_PREFIX TYPE CATEGORY CUSTOM_ITEM_SUFFIX MODS CRAFT WHITEBLACKLIST CHARGES + //~ FOOD_TRAITS FOOD_STATUS FOOD_IRRADIATED TEMPERATURE LOCATION_HINT CLOTHING_SIZE ETHEREAL + //~ FILTHY BROKEN CBM_STATUS UPS TAGS VARS WETNESS ACTIVE SEALED FAVORITE_POST RELIC LINK + //~ TECHNIQUES + //~ -- + //~ refer to io::enum_to_string for an updated list + std::string order_i18n( _( "tname_segments_order" ) ); + if( order_i18n != "tname_segments_order" ) { + std::stringstream ss( order_i18n ); + std::istream_iterator begin( ss ); + std::istream_iterator end; + std::vector tokens( begin, end ); + + tname_array tna; + tna.fill( 999 ); + int cur_order = 0; + for( std::string const &s : tokens ) { + if( std::optional idx = str_to_segment_idx( s ); idx ) { + tna[*idx] = cur_order++; + } else { + DebugLog( D_WARNING, D_MAIN ) << "Ignoring tname segment " << std::quoted( s ) << std::endl; + } + } + + std::stable_sort( tns.begin(), tns.end(), segment_order( tna ) ); + } + for( std::size_t i = static_cast( fixed_pos_segments ); + i < static_cast( tname::segments::last_segment ); i++ ) { + tns.emplace_back( static_cast( i ) ); + } + } + + return tns; +} } // namespace tname diff --git a/src/item_tname.h b/src/item_tname.h index 9d59f8247a38c..cdee759dfe120 100644 --- a/src/item_tname.h +++ b/src/item_tname.h @@ -79,6 +79,9 @@ using segment_bitset = enum_bitset; std::string print_segment( tname::segments segment, item const &it, unsigned int quantity, segment_bitset const &segments ); +using tname_set = std::vector; +tname_set const &get_tname_set(); + #endif // CATA_IN_TOOL } // namespace tname diff --git a/tests/item_tname_test.cpp b/tests/item_tname_test.cpp index 6c85312b98624..d8a6f54efb8d5 100644 --- a/tests/item_tname_test.cpp +++ b/tests/item_tname_test.cpp @@ -947,3 +947,20 @@ TEST_CASE( "nested_items_tname", "[item][tname]" ) } } } + +#ifdef LOCALIZE +TEST_CASE( "tname_i18n_order", "[item][tname][translations]" ) +{ + item backpack( "backpack" ); + backpack.burnt = 1; + backpack.set_flag( flag_FILTHY ); + REQUIRE( backpack.tname() == "++ burnt backpack (filthy)" ); + + set_language( "ru" ); + TranslationManager::GetInstance().LoadDocuments( { "./data/mods/TEST_DATA/lang/mo/ru/LC_MESSAGES/TEST_DATA.mo" } ); + CHECK( backpack.tname() == + "++ backpack (burnt) (filthy)" ); + + set_language_from_options(); +} +#endif