2024-07-20 14:55:17 +02:00
/*
* TextLocalizationContainer . cpp , part of VCMI engine
*
* Authors : listed in file AUTHORS in main folder
*
* License : GNU General Public License v2 .0 or later
* Full text of license available in license . txt file , in main folder
*
*/
# include "StdInc.h"
# include "TextLocalizationContainer.h"
# include "texts/CGeneralTextHandler.h"
# include "Languages.h"
# include "TextOperations.h"
# include "../VCMI_Lib.h"
# include "../json/JsonNode.h"
# include "../modding/CModHandler.h"
VCMI_LIB_NAMESPACE_BEGIN
std : : recursive_mutex TextLocalizationContainer : : globalTextMutex ;
2024-09-30 12:21:10 +02:00
void TextLocalizationContainer : : registerStringOverride ( const std : : string & modContext , const TextIdentifier & UID , const std : : string & localized )
2024-07-20 14:55:17 +02:00
{
std : : lock_guard globalLock ( globalTextMutex ) ;
assert ( ! modContext . empty ( ) ) ;
// NOTE: implicitly creates entry, intended - strings added by maps, campaigns, vcmi and potentially - UI mods are not registered anywhere at the moment
auto & entry = stringsLocalizations [ UID . get ( ) ] ;
2024-09-30 19:07:54 +02:00
// load string override only in following cases:
// a) string was not modified in another mod (e.g. rebalance mod gave skill new description)
// b) this string override is defined in the same mod as one that provided modified version of this string
if ( entry . identifierModContext = = entry . baseStringModContext | | modContext = = entry . baseStringModContext )
{
entry . translatedText = localized ;
if ( entry . identifierModContext . empty ( ) )
{
entry . identifierModContext = modContext ;
entry . baseStringModContext = modContext ;
}
}
else
{
logGlobal - > debug ( " Skipping translation override for string %s: changed in a different mod " , UID . get ( ) ) ;
}
2024-07-20 14:55:17 +02:00
}
void TextLocalizationContainer : : addSubContainer ( const TextLocalizationContainer & container )
{
std : : lock_guard globalLock ( globalTextMutex ) ;
assert ( ! vstd : : contains ( subContainers , & container ) ) ;
subContainers . push_back ( & container ) ;
}
void TextLocalizationContainer : : removeSubContainer ( const TextLocalizationContainer & container )
{
std : : lock_guard globalLock ( globalTextMutex ) ;
assert ( vstd : : contains ( subContainers , & container ) ) ;
subContainers . erase ( std : : remove ( subContainers . begin ( ) , subContainers . end ( ) , & container ) , subContainers . end ( ) ) ;
}
2024-09-30 19:07:54 +02:00
const std : : string & TextLocalizationContainer : : translateString ( const TextIdentifier & identifier ) const
2024-07-20 14:55:17 +02:00
{
std : : lock_guard globalLock ( globalTextMutex ) ;
if ( stringsLocalizations . count ( identifier . get ( ) ) = = 0 )
{
for ( auto containerIter = subContainers . rbegin ( ) ; containerIter ! = subContainers . rend ( ) ; + + containerIter )
if ( ( * containerIter ) - > identifierExists ( identifier ) )
2024-09-30 19:07:54 +02:00
return ( * containerIter ) - > translateString ( identifier ) ;
2024-07-20 14:55:17 +02:00
logGlobal - > error ( " Unable to find localization for string '%s' " , identifier . get ( ) ) ;
return identifier . get ( ) ;
}
const auto & entry = stringsLocalizations . at ( identifier . get ( ) ) ;
2024-09-30 19:07:54 +02:00
return entry . translatedText ;
2024-07-20 14:55:17 +02:00
}
2024-09-30 12:21:10 +02:00
void TextLocalizationContainer : : registerString ( const std : : string & modContext , const TextIdentifier & inputUID , const JsonNode & localized )
{
2024-09-30 19:07:54 +02:00
assert ( localized . isNull ( ) | | ! localized . getModScope ( ) . empty ( ) ) ;
assert ( localized . isNull ( ) | | ! getModLanguage ( localized . getModScope ( ) ) . empty ( ) ) ;
2024-09-30 12:21:10 +02:00
2024-09-30 19:07:54 +02:00
if ( localized . isNull ( ) )
registerString ( modContext , modContext , inputUID , localized . String ( ) ) ;
else
registerString ( modContext , localized . getModScope ( ) , inputUID , localized . String ( ) ) ;
2024-09-30 12:21:10 +02:00
}
2024-09-30 19:07:54 +02:00
void TextLocalizationContainer : : registerString ( const std : : string & modContext , const TextIdentifier & UID , const std : : string & localized )
{
registerString ( modContext , modContext , UID , localized ) ;
}
void TextLocalizationContainer : : registerString ( const std : : string & identifierModContext , const std : : string & localizedStringModContext , const TextIdentifier & UID , const std : : string & localized )
2024-07-20 14:55:17 +02:00
{
std : : lock_guard globalLock ( globalTextMutex ) ;
2024-09-30 19:07:54 +02:00
assert ( ! identifierModContext . empty ( ) ) ;
assert ( ! localizedStringModContext . empty ( ) ) ;
2024-07-20 14:55:17 +02:00
assert ( UID . get ( ) . find ( " .. " ) = = std : : string : : npos ) ; // invalid identifier - there is section that was evaluated to empty string
2024-10-14 20:59:09 +02:00
assert ( stringsLocalizations . count ( UID . get ( ) ) = = 0 | | boost : : algorithm : : starts_with ( UID . get ( ) , " map " ) | | boost : : algorithm : : starts_with ( UID . get ( ) , " header " ) ) ; // registering already registered string? FIXME: "header" is a workaround. VMAP needs proper integration in translation system
2024-07-20 14:55:17 +02:00
if ( stringsLocalizations . count ( UID . get ( ) ) > 0 )
{
auto & value = stringsLocalizations [ UID . get ( ) ] ;
2024-09-30 19:07:54 +02:00
value . translatedText = localized ;
value . identifierModContext = identifierModContext ;
value . baseStringModContext = localizedStringModContext ;
2024-07-20 14:55:17 +02:00
}
else
{
StringState value ;
2024-09-30 19:07:54 +02:00
value . translatedText = localized ;
value . identifierModContext = identifierModContext ;
value . baseStringModContext = localizedStringModContext ;
2024-07-20 14:55:17 +02:00
stringsLocalizations [ UID . get ( ) ] = value ;
}
}
2024-09-30 12:21:10 +02:00
void TextLocalizationContainer : : loadTranslationOverrides ( const std : : string & modContext , const JsonNode & config )
2024-07-20 14:55:17 +02:00
{
for ( const auto & node : config . Struct ( ) )
2024-09-30 12:21:10 +02:00
registerStringOverride ( modContext , node . first , node . second . String ( ) ) ;
2024-07-20 14:55:17 +02:00
}
bool TextLocalizationContainer : : identifierExists ( const TextIdentifier & UID ) const
{
std : : lock_guard globalLock ( globalTextMutex ) ;
return stringsLocalizations . count ( UID . get ( ) ) ;
}
void TextLocalizationContainer : : exportAllTexts ( std : : map < std : : string , std : : map < std : : string , std : : string > > & storage ) const
{
std : : lock_guard globalLock ( globalTextMutex ) ;
for ( auto const & subContainer : subContainers )
subContainer - > exportAllTexts ( storage ) ;
for ( auto const & entry : stringsLocalizations )
{
std : : string textToWrite ;
2024-09-30 19:07:54 +02:00
std : : string modName = entry . second . baseStringModContext ;
2024-07-20 14:55:17 +02:00
2024-10-10 22:31:11 +02:00
if ( entry . second . baseStringModContext = = entry . second . identifierModContext & & modName . find ( ' . ' ) ! = std : : string : : npos )
modName = modName . substr ( 0 , modName . find ( ' . ' ) ) ;
2024-09-30 19:07:54 +02:00
boost : : range : : replace ( modName , ' . ' , ' _ ' ) ;
2024-07-20 14:55:17 +02:00
2024-09-30 19:07:54 +02:00
textToWrite = entry . second . translatedText ;
2024-07-20 14:55:17 +02:00
2024-09-30 19:07:54 +02:00
if ( ! textToWrite . empty ( ) )
storage [ modName ] [ entry . first ] = textToWrite ;
2024-07-20 14:55:17 +02:00
}
}
std : : string TextLocalizationContainer : : getModLanguage ( const std : : string & modContext )
{
if ( modContext = = " core " )
return CGeneralTextHandler : : getInstalledLanguage ( ) ;
return VLC - > modh - > getModLanguage ( modContext ) ;
}
void TextLocalizationContainer : : jsonSerialize ( JsonNode & dest ) const
{
std : : lock_guard globalLock ( globalTextMutex ) ;
for ( auto & s : stringsLocalizations )
2024-09-30 19:07:54 +02:00
dest . Struct ( ) [ s . first ] . String ( ) = s . second . translatedText ;
2024-07-20 14:55:17 +02:00
}
TextContainerRegistrable : : TextContainerRegistrable ( )
{
VLC - > generaltexth - > addSubContainer ( * this ) ;
}
TextContainerRegistrable : : ~ TextContainerRegistrable ( )
{
VLC - > generaltexth - > removeSubContainer ( * this ) ;
}
TextContainerRegistrable : : TextContainerRegistrable ( const TextContainerRegistrable & other )
: TextLocalizationContainer ( other )
{
VLC - > generaltexth - > addSubContainer ( * this ) ;
}
TextContainerRegistrable : : TextContainerRegistrable ( TextContainerRegistrable & & other ) noexcept
: TextLocalizationContainer ( other )
{
VLC - > generaltexth - > addSubContainer ( * this ) ;
}
VCMI_LIB_NAMESPACE_END