Просмотр исходного кода

All spell texts are now passed through translator

Ivan Savenko 2 лет назад
Родитель
Сommit
5da407e822

+ 2 - 2
AI/BattleAI/BattleAI.cpp

@@ -687,7 +687,7 @@ void CBattleAI::attemptCastingSpell()
 
 	if(castToPerform.value > 0)
 	{
-		LOGFL("Best spell is %s (value %d). Will cast.", castToPerform.spell->name % castToPerform.value);
+		LOGFL("Best spell is %s (value %d). Will cast.", castToPerform.spell->getNameTranslated() % castToPerform.value);
 		BattleAction spellcast;
 		spellcast.actionType = EActionType::HERO_SPELL;
 		spellcast.actionSubtype = castToPerform.spell->id;
@@ -698,7 +698,7 @@ void CBattleAI::attemptCastingSpell()
 	}
 	else
 	{
-		LOGFL("Best spell is %s. But it is actually useless (value %d).", castToPerform.spell->name % castToPerform.value);
+		LOGFL("Best spell is %s. But it is actually useless (value %d).", castToPerform.spell->getNameTranslated() % castToPerform.value);
 	}
 }
 

+ 5 - 5
AI/Nullkiller/Goals/AdventureSpellCast.cpp

@@ -33,16 +33,16 @@ void AdventureSpellCast::accept(AIGateway * ai)
 
 	auto spell = getSpell();
 
-	logAi->trace("Decomposing adventure spell cast of %s for hero %s", spell->name, hero->name);
+	logAi->trace("Decomposing adventure spell cast of %s for hero %s", spell->getNameTranslated(), hero->name);
 
 	if(!spell->isAdventure())
-		throw cannotFulfillGoalException(spell->name + " is not an adventure spell.");
+		throw cannotFulfillGoalException(spell->getNameTranslated() + " is not an adventure spell.");
 
 	if(!hero->canCastThisSpell(spell))
-		throw cannotFulfillGoalException("Hero can not cast " + spell->name);
+		throw cannotFulfillGoalException("Hero can not cast " + spell->getNameTranslated());
 
 	if(hero->mana < hero->getSpellCost(spell))
-		throw cannotFulfillGoalException("Hero has not enough mana to cast " + spell->name);
+		throw cannotFulfillGoalException("Hero has not enough mana to cast " + spell->getNameTranslated());
 
 	if(spellID == SpellID::TOWN_PORTAL && town && town->visitingHero)
 		throw cannotFulfillGoalException("The town is already occupied by " + town->visitingHero->name);
@@ -70,7 +70,7 @@ void AdventureSpellCast::accept(AIGateway * ai)
 
 std::string AdventureSpellCast::toString() const
 {
-	return "AdventureSpellCast " + spellID.toSpell()->name;
+	return "AdventureSpellCast " + spellID.toSpell()->getNameTranslated();
 }
 
 }

+ 5 - 5
client/battle/BattleActionsController.cpp

@@ -484,7 +484,7 @@ void BattleActionsController::handleHex(BattleHex myNumber, int eventType)
 				break;
 			case PossiblePlayerBattleAction::AIMED_SPELL_CREATURE:
 				currentSpell = CGI->spellh->objects[creatureCasting ? owner.stacksController->activeStackSpellToCast() : spellToCast->actionSubtype]; //necessary if creature has random Genie spell at same time
-				newConsoleMsg = boost::str(boost::format(CGI->generaltexth->allTexts[27]) % currentSpell->name % shere->getName()); //Cast %s on %s
+				newConsoleMsg = boost::str(boost::format(CGI->generaltexth->allTexts[27]) % currentSpell->getNameTranslated() % shere->getName()); //Cast %s on %s
 				switch (currentSpell->id)
 				{
 					case SpellID::SACRIFICE:
@@ -497,7 +497,7 @@ void BattleActionsController::handleHex(BattleHex myNumber, int eventType)
 				break;
 			case PossiblePlayerBattleAction::ANY_LOCATION:
 				currentSpell = CGI->spellh->objects[creatureCasting ? owner.stacksController->activeStackSpellToCast() : spellToCast->actionSubtype]; //necessary if creature has random Genie spell at same time
-				newConsoleMsg = boost::str(boost::format(CGI->generaltexth->allTexts[26]) % currentSpell->name); //Cast %s
+				newConsoleMsg = boost::str(boost::format(CGI->generaltexth->allTexts[26]) % currentSpell->getNameTranslated()); //Cast %s
 				isCastingPossible = true;
 				break;
 			case PossiblePlayerBattleAction::RANDOM_GENIE_SPELL: //we assume that teleport / sacrifice will never be available as random spell
@@ -522,7 +522,7 @@ void BattleActionsController::handleHex(BattleHex myNumber, int eventType)
 				isCastingPossible = true;
 				break;
 			case PossiblePlayerBattleAction::FREE_LOCATION:
-				newConsoleMsg = boost::str(boost::format(CGI->generaltexth->allTexts[26]) % currentSpell->name); //Cast %s
+				newConsoleMsg = boost::str(boost::format(CGI->generaltexth->allTexts[26]) % currentSpell->getNameTranslated()); //Cast %s
 				isCastingPossible = true;
 				break;
 			case PossiblePlayerBattleAction::HEAL:
@@ -561,7 +561,7 @@ void BattleActionsController::handleHex(BattleHex myNumber, int eventType)
 				break;
 			case PossiblePlayerBattleAction::FREE_LOCATION:
 				cursorFrame = Cursor::Combat::BLOCKED;
-				newConsoleMsg = boost::str(boost::format(CGI->generaltexth->allTexts[181]) % currentSpell->name); //No room to place %s here
+				newConsoleMsg = boost::str(boost::format(CGI->generaltexth->allTexts[181]) % currentSpell->getNameTranslated()); //No room to place %s here
 				break;
 			default:
 				if (myNumber == -1)
@@ -582,7 +582,7 @@ void BattleActionsController::handleHex(BattleHex myNumber, int eventType)
 			default:
 				spellcastingCursor = true;
 				if (newConsoleMsg.empty() && currentSpell)
-					newConsoleMsg = boost::str(boost::format(CGI->generaltexth->allTexts[26]) % currentSpell->name); //Cast %s
+					newConsoleMsg = boost::str(boost::format(CGI->generaltexth->allTexts[26]) % currentSpell->getNameTranslated()); //Cast %s
 				break;
 		}
 

+ 2 - 2
client/lobby/CBonusSelection.cpp

@@ -176,7 +176,7 @@ void CBonusSelection::createBonusesIcons()
 		{
 		case CScenarioTravel::STravelBonus::SPELL:
 			desc = CGI->generaltexth->allTexts[715];
-			boost::algorithm::replace_first(desc, "%s", CGI->spells()->getByIndex(bonDescs[i].info2)->getName());
+			boost::algorithm::replace_first(desc, "%s", CGI->spells()->getByIndex(bonDescs[i].info2)->getNameTranslated());
 			break;
 		case CScenarioTravel::STravelBonus::MONSTER:
 			picNumber = bonDescs[i].info2 + 2;
@@ -213,7 +213,7 @@ void CBonusSelection::createBonusesIcons()
 			break;
 		case CScenarioTravel::STravelBonus::SPELL_SCROLL:
 			desc = CGI->generaltexth->allTexts[716];
-			boost::algorithm::replace_first(desc, "%s", CGI->spells()->getByIndex(bonDescs[i].info2)->getName());
+			boost::algorithm::replace_first(desc, "%s", CGI->spells()->getByIndex(bonDescs[i].info2)->getNameTranslated());
 			break;
 		case CScenarioTravel::STravelBonus::PRIMARY_SKILL:
 		{

+ 3 - 2
client/widgets/CComponent.cpp

@@ -25,6 +25,7 @@
 
 #include "../../lib/CArtHandler.h"
 #include "../../lib/CTownHandler.h"
+#include "../../lib/spells/CSpellHandler.h"
 #include "../../lib/CCreatureHandler.h"
 #include "../../lib/CSkillHandler.h"
 #include "../../lib/CGeneralTextHandler.h"
@@ -168,7 +169,7 @@ std::string CComponent::getDescription()
 		return art->getEffectiveDescription();
 	}
 	case experience: return CGI->generaltexth->allTexts[241];
-	case spell:      return SpellID(subtype).toSpell(CGI->spells())->getLevelDescription(val);
+	case spell:      return (*CGI->spellh)[subtype]->getDescriptionTranslated(val);
 	case morale:     return CGI->generaltexth->heroscrn[ 4 - (val>0) + (val<0)];
 	case luck:       return CGI->generaltexth->heroscrn[ 7 - (val>0) + (val<0)];
 	case building:   return (*CGI->townh)[subtype]->town->buildings[BuildingID(val)]->Description();
@@ -212,7 +213,7 @@ std::string CComponent::getSubtitleInternal()
 				return boost::lexical_cast<std::string>(val); //amount of experience OR level required for seer hut;
 			}
 		}
-	case spell:      return CGI->spells()->getByIndex(subtype)->getName();
+	case spell:      return CGI->spells()->getByIndex(subtype)->getNameTranslated();
 	case morale:     return "";
 	case luck:       return "";
 	case building:

+ 3 - 3
client/windows/CCastleInterface.cpp

@@ -1771,19 +1771,19 @@ CMageGuildScreen::Scroll::Scroll(Point position, const CSpell *Spell)
 void CMageGuildScreen::Scroll::clickLeft(tribool down, bool previousState)
 {
 	if(down)
-		LOCPLINT->showInfoDialog(spell->getLevelDescription(0), std::make_shared<CComponent>(CComponent::spell, spell->id));
+		LOCPLINT->showInfoDialog(spell->getDescriptionTranslated(0), std::make_shared<CComponent>(CComponent::spell, spell->id));
 }
 
 void CMageGuildScreen::Scroll::clickRight(tribool down, bool previousState)
 {
 	if(down)
-		CRClickPopup::createAndPush(spell->getLevelDescription(0), std::make_shared<CComponent>(CComponent::spell, spell->id));
+		CRClickPopup::createAndPush(spell->getDescriptionTranslated(0), std::make_shared<CComponent>(CComponent::spell, spell->id));
 }
 
 void CMageGuildScreen::Scroll::hover(bool on)
 {
 	if(on)
-		GH.statusbar->write(spell->name);
+		GH.statusbar->write(spell->getNameTranslated());
 	else
 		GH.statusbar->clear();
 

+ 1 - 1
client/windows/CCreatureWindow.cpp

@@ -206,7 +206,7 @@ CStackWindow::ActiveSpellsSection::ActiveSpellsSection(CStackWindow * owner, int
 		if (hasGraphics)
 		{
 			spellText = CGI->generaltexth->allTexts[610]; //"%s, duration: %d rounds."
-			boost::replace_first(spellText, "%s", spell->getName());
+			boost::replace_first(spellText, "%s", spell->getNameTranslated());
 			//FIXME: support permanent duration
 			int duration = battleStack->getBonusLocalFirst(Selector::source(Bonus::SPELL_EFFECT,effect))->turnsRemain;
 			boost::replace_first(spellText, "%d", boost::lexical_cast<std::string>(duration));

+ 5 - 5
client/windows/CSpellWindow.cpp

@@ -90,7 +90,7 @@ public:
 				return false;
 		}
 
-		return A->name < B->name;
+		return A->getNameTranslated() < B->getNameTranslated();
 	}
 } spellsorter;
 
@@ -545,7 +545,7 @@ void CSpellWindow::SpellArea::clickLeft(tribool down, bool previousState)
 		if((combatSpell ^ inCombat) || inCastle)
 		{
 			std::vector<std::shared_ptr<CComponent>> hlp(1, std::make_shared<CComponent>(CComponent::spell, mySpell->id, 0));
-			owner->myInt->showInfoDialog(mySpell->getLevelDescription(schoolLevel), hlp);
+			owner->myInt->showInfoDialog(mySpell->getDescriptionTranslated(schoolLevel), hlp);
 		}
 		else if(combatSpell)
 		{
@@ -600,7 +600,7 @@ void CSpellWindow::SpellArea::clickRight(tribool down, bool previousState)
 			boost::algorithm::replace_first(dmgInfo, "%d", boost::lexical_cast<std::string>(causedDmg));
 		}
 
-		CRClickPopup::createAndPush(mySpell->getLevelDescription(schoolLevel) + dmgInfo, std::make_shared<CComponent>(CComponent::spell, mySpell->id));
+		CRClickPopup::createAndPush(mySpell->getDescriptionTranslated(schoolLevel) + dmgInfo, std::make_shared<CComponent>(CComponent::spell, mySpell->id));
 	}
 }
 
@@ -609,7 +609,7 @@ void CSpellWindow::SpellArea::hover(bool on)
 	if(mySpell)
 	{
 		if(on)
-			owner->statusBar->write(boost::to_string(boost::format("%s (%s)") % mySpell->name % CGI->generaltexth->allTexts[171+mySpell->level]));
+			owner->statusBar->write(boost::to_string(boost::format("%s (%s)") % mySpell->getNameTranslated() % CGI->generaltexth->allTexts[171+mySpell->level]));
 		else
 			owner->statusBar->clear();
 	}
@@ -651,7 +651,7 @@ void CSpellWindow::SpellArea::setSpell(const CSpell * spell)
 		}
 
 		name->color = firstLineColor;
-		name->setText(mySpell->name);
+		name->setText(mySpell->getNameTranslated());
 
 		level->color = secondLineColor;
 		if(schoolLevel > 0)

+ 8 - 1
include/vcmi/spells/Spell.h

@@ -23,6 +23,8 @@ class Caster;
 
 class DLL_LINKAGE Spell: public EntityT<SpellID>
 {
+	using EntityT<SpellID>::getName;
+
 public:
 	using SchoolCallback = std::function<void(const SchoolInfo &, bool &)>;
 
@@ -51,7 +53,12 @@ public:
 	 * Returns spell level power, base power ignored
 	 */
 	virtual int32_t getLevelPower(const int32_t skillLevel) const = 0;
-	virtual const std::string & getLevelDescription(const int32_t skillLevel) const = 0;
+
+	virtual std::string getNameTextID() const = 0;
+	virtual std::string getNameTranslated() const  = 0;
+
+	virtual std::string getDescriptionTextID(int32_t level) const  = 0;
+	virtual std::string getDescriptionTranslated(int32_t level) const  = 0;
 
 };
 

+ 1 - 1
lib/CArtHandler.cpp

@@ -769,7 +769,7 @@ std::string CArtifactInstance::getEffectiveDescription(const CGHeroInstance * he
 		if(spellID.getNum() >= 0)
 		{
 			if(nameStart != std::string::npos  &&  nameEnd != std::string::npos)
-				text = text.replace(nameStart, nameEnd - nameStart + 1, spellID.toSpell(VLC->spells())->getName());
+				text = text.replace(nameStart, nameEnd - nameStart + 1, spellID.toSpell(VLC->spells())->getNameTranslated());
 		}
 	}
 	else if(hero && artType->constituentOf.size()) //display info about set

+ 1 - 1
lib/CBonusTypeHandler.cpp

@@ -150,7 +150,7 @@ std::string CBonusTypeHandler::bonusToString(const std::shared_ptr<Bonus> & bonu
 		else if(name == "subtype.spell")
 		{
 			 const SpellID sp(bonus->subtype);
-			 return sp.toSpell()->name;
+			 return sp.toSpell()->getNameTranslated();
 		}
 		else if(name == "MR")
 		{

+ 1 - 1
lib/CGameState.cpp

@@ -123,7 +123,7 @@ void MetaString::getLocalString(const std::pair<ui8,ui32> &txt, std::string &dst
 	{
 		auto spell = SpellID(ser).toSpell(VLC->spells());
 		if(spell)
-			dst = spell->getName();
+			dst = spell->getNameTranslated();
 		else
 			dst = "#!#";
 	}

+ 1 - 1
lib/HeroBonus.cpp

@@ -1597,7 +1597,7 @@ std::string Bonus::Description() const
 				str << ArtifactID(sid).toArtifact(VLC->artifacts())->getName();
 				break;
 			case SPELL_EFFECT:
-				str << SpellID(sid).toSpell(VLC->spells())->getName();
+				str << SpellID(sid).toSpell(VLC->spells())->getNameTranslated();
 				break;
 			case CREATURE_ABILITY:
 				str << VLC->creh->objects[sid]->namePl;

+ 5 - 5
lib/mapObjects/CGHeroInstance.cpp

@@ -737,7 +737,7 @@ bool CGHeroInstance::canCastThisSpell(const spells::Spell * spell) const
 	{
 		if(inSpellBook)
 		{//hero has this spell in spellbook
-			logGlobal->error("Special spell %s in spellbook.", spell->getName());
+			logGlobal->error("Special spell %s in spellbook.", spell->getNameTranslated());
 		}
 		return specificBonus;
 	}
@@ -747,7 +747,7 @@ bool CGHeroInstance::canCastThisSpell(const spells::Spell * spell) const
 		{
 			//hero has this spell in spellbook
 			//it is normal if set in map editor, but trace it to possible debug of magic guild
-			logGlobal->trace("Banned spell %s in spellbook.", spell->getName());
+			logGlobal->trace("Banned spell %s in spellbook.", spell->getNameTranslated());
 		}
 		return inSpellBook || specificBonus || schoolBonus || levelBonus;
 	}
@@ -770,19 +770,19 @@ bool CGHeroInstance::canLearnSpell(const spells::Spell * spell) const
 
 	if(spell->isSpecial())
 	{
-		logGlobal->warn("Hero %s try to learn special spell %s", nodeName(), spell->getName());
+		logGlobal->warn("Hero %s try to learn special spell %s", nodeName(), spell->getNameTranslated());
 		return false;//special spells can not be learned
 	}
 
 	if(spell->isCreatureAbility())
 	{
-		logGlobal->warn("Hero %s try to learn creature spell %s", nodeName(), spell->getName());
+		logGlobal->warn("Hero %s try to learn creature spell %s", nodeName(), spell->getNameTranslated());
 		return false;//creature abilities can not be learned
 	}
 
 	if(!IObjectInterface::cb->isAllowed(0, spell->getIndex()))
 	{
-		logGlobal->warn("Hero %s try to learn banned spell %s", nodeName(), spell->getName());
+		logGlobal->warn("Hero %s try to learn banned spell %s", nodeName(), spell->getNameTranslated());
 		return false;//banned spells should not be learned
 	}
 

+ 1 - 1
lib/mapObjects/MiscObjects.cpp

@@ -1663,7 +1663,7 @@ std::string CGShrine::getHoverText(PlayerColor player) const
 	if(wasVisited(player))
 	{
 		hoverName += "\n" + VLC->generaltexth->allTexts[355]; // + (learn %s)
-		boost::algorithm::replace_first(hoverName,"%s", spell.toSpell()->name);
+		boost::algorithm::replace_first(hoverName,"%s", spell.toSpell()->getNameTextID());
 	}
 	return hoverName;
 }

+ 39 - 21
lib/spells/CSpellHandler.cpp

@@ -85,8 +85,7 @@ static const ESpellSchool SCHOOL_ORDER[4] =
 } //namespace SpellConfig
 
 ///CSpell::LevelInfo
-CSpell::LevelInfo::LevelInfo()
-	: description(""),
+CSpell::LevelInfo::LevelInfo():
 	cost(0),
 	power(0),
 	AIValue(0),
@@ -204,7 +203,29 @@ SpellID CSpell::getId() const
 
 const std::string & CSpell::getName() const
 {
-	return name;
+	return identifier;
+}
+
+std::string CSpell::getNameTextID() const
+{
+	TextIdentifier id("spell", modScope, identifier, "name");
+	return id.get();
+}
+
+std::string CSpell::getNameTranslated() const
+{
+	return VLC->generaltexth->translate(getNameTextID());
+}
+
+std::string CSpell::getDescriptionTextID(int32_t level) const
+{
+	TextIdentifier id("spell", modScope, identifier, "description", SpellConfig::LEVEL_NAMES[level]);
+	return id.get();
+}
+
+std::string CSpell::getDescriptionTranslated(int32_t level) const
+{
+	return VLC->generaltexth->translate(getDescriptionTextID(level));
 }
 
 const std::string & CSpell::getJsonKey() const
@@ -340,11 +361,6 @@ int32_t CSpell::getLevelPower(const int32_t skillLevel) const
 	return getLevelInfo(skillLevel).power;
 }
 
-const std::string & CSpell::getLevelDescription(const int32_t skillLevel) const
-{
-	return getLevelInfo(skillLevel).description;
-}
-
 si32 CSpell::getProbability(const TFaction factionId) const
 {
 	if(!vstd::contains(probabilities,factionId))
@@ -366,7 +382,7 @@ void CSpell::getEffects(std::vector<Bonus> & lst, const int level, const bool cu
 
 	if(levelObject.effects.empty() && levelObject.cumulativeEffects.empty())
 	{
-		logGlobal->error("This spell (%s) has no effects for level %d", name, level);
+		logGlobal->error("This spell (%s) has no effects for level %d", getNameTranslated(), level);
 		return;
 	}
 
@@ -702,6 +718,7 @@ CSpell * CSpellHandler::loadFromJson(const std::string & scope, const JsonNode &
 	CSpell * spell = new CSpell();
 	spell->id = id;
 	spell->identifier = identifier;
+	spell->modScope = scope;
 
 	const auto type = json["type"].String();
 
@@ -716,9 +733,9 @@ CSpell * CSpellHandler::loadFromJson(const std::string & scope, const JsonNode &
 		spell->combat = type == "combat";
 	}
 
-	spell->name = json["name"].String();
+	VLC->generaltexth->registerString(spell->getNameTextID(), json["name"].String());
 
-	logMod->trace("%s: loading spell %s", __FUNCTION__, spell->name);
+	logMod->trace("%s: loading spell %s", __FUNCTION__, spell->getNameTranslated());
 
 	const auto schoolNames = json["school"];
 
@@ -753,7 +770,7 @@ CSpell * CSpellHandler::loadFromJson(const std::string & scope, const JsonNode &
 	else if(targetType == "LOCATION")
 		spell->targetType = spells::AimType::LOCATION;
 	else
-		logMod->warn("Spell %s: target type %s - assumed NO_TARGET.", spell->name, (targetType.empty() ? "empty" : "unknown ("+targetType+")"));
+		logMod->warn("Spell %s: target type %s - assumed NO_TARGET.", spell->getNameTranslated(), (targetType.empty() ? "empty" : "unknown ("+targetType+")"));
 
 	for(const auto & counteredSpell: json["counters"].Struct())
 	{
@@ -800,7 +817,7 @@ CSpell * CSpellHandler::loadFromJson(const std::string & scope, const JsonNode &
 	else if(!implicitPositiveness)
 	{
 		spell->positiveness = CSpell::NEUTRAL; //duplicates constructor but, just in case
-		logMod->error("Spell %s: no positiveness specified, assumed NEUTRAL.", spell->name);
+		logMod->error("Spell %s: no positiveness specified, assumed NEUTRAL.", spell->getNameTranslated());
 	}
 
 	spell->special = flags["special"].Bool();
@@ -810,7 +827,7 @@ CSpell * CSpellHandler::loadFromJson(const std::string & scope, const JsonNode &
 		auto it = bonusNameMap.find(name);
 		if(it == bonusNameMap.end())
 		{
-			logMod->error("Spell %s: invalid bonus name %s", spell->name, name);
+			logMod->error("Spell %s: invalid bonus name %s", spell->getNameTranslated(), name);
 		}
 		else
 		{
@@ -844,7 +861,7 @@ CSpell * CSpellHandler::loadFromJson(const std::string & scope, const JsonNode &
 
 		if(!(immunities.empty() && absoluteImmunities.empty() && limiters.empty() && absoluteLimiters.empty()))
 		{
-			logMod->warn("Spell %s has old target condition format. Expected configuration: ", spell->name);
+			logMod->warn("Spell %s has old target condition format. Expected configuration: ", spell->getNameTranslated());
 			spell->targetCondition = spell->convertTargetCondition(immunities, absoluteImmunities, limiters, absoluteLimiters);
 			logMod->warn("\n\"targetCondition\" : %s", spell->targetCondition.toJson());
 		}
@@ -855,13 +872,13 @@ CSpell * CSpellHandler::loadFromJson(const std::string & scope, const JsonNode &
 
 		//TODO: could this be safely merged instead of discarding?
 		if(!json["immunity"].isNull())
-			logMod->warn("Spell %s 'immunity' field mixed with 'targetCondition' discarded", spell->name);
+			logMod->warn("Spell %s 'immunity' field mixed with 'targetCondition' discarded", spell->getNameTranslated());
 		if(!json["absoluteImmunity"].isNull())
-			logMod->warn("Spell %s 'absoluteImmunity' field mixed with 'targetCondition' discarded", spell->name);
+			logMod->warn("Spell %s 'absoluteImmunity' field mixed with 'targetCondition' discarded", spell->getNameTranslated());
 		if(!json["limit"].isNull())
-			logMod->warn("Spell %s 'limit' field mixed with 'targetCondition' discarded", spell->name);
+			logMod->warn("Spell %s 'limit' field mixed with 'targetCondition' discarded", spell->getNameTranslated());
 		if(!json["absoluteLimit"].isNull())
-			logMod->warn("Spell %s 'absoluteLimit' field mixed with 'targetCondition' discarded", spell->name);
+			logMod->warn("Spell %s 'absoluteLimit' field mixed with 'targetCondition' discarded", spell->getNameTranslated());
 	}
 
 	const JsonNode & graphicsNode = json["graphics"];
@@ -930,7 +947,8 @@ CSpell * CSpellHandler::loadFromJson(const std::string & scope, const JsonNode &
 
 		const si32 levelPower     = levelObject.power = static_cast<si32>(levelNode["power"].Integer());
 
-		levelObject.description   = levelNode["description"].String();
+		VLC->generaltexth->registerString(spell->getDescriptionTranslated(levelIndex), levelNode["description"].String());
+
 		levelObject.cost          = static_cast<si32>(levelNode["cost"].Integer());
 		levelObject.AIValue       = static_cast<si32>(levelNode["aiValue"].Integer());
 		levelObject.smartTarget   = levelNode["targetModifier"]["smart"].Bool();
@@ -973,7 +991,7 @@ CSpell * CSpellHandler::loadFromJson(const std::string & scope, const JsonNode &
 			levelObject.battleEffects = levelNode["battleEffects"];
 
 			if(!levelObject.cumulativeEffects.empty() || !levelObject.effects.empty() || spell->isOffensive())
-				logGlobal->error("Mixing %s special effects with old format effects gives unpredictable result", spell->name);
+				logGlobal->error("Mixing %s special effects with old format effects gives unpredictable result", spell->getNameTranslated());
 		}
 	}
 	return spell;

+ 11 - 10
lib/spells/CSpellHandler.h

@@ -126,7 +126,6 @@ public:
 public:
 	struct LevelInfo
 	{
-		std::string description; //descriptions of spell for skill level
 		si32 cost;
 		si32 power;
 		si32 AIValue;
@@ -147,7 +146,6 @@ public:
 
 		template <typename Handler> void serialize(Handler & h, const int version)
 		{
-			h & description;
 			h & cost;
 			h & power;
 			h & AIValue;
@@ -168,6 +166,11 @@ public:
 	 *
 	 */
 	const CSpell::LevelInfo & getLevelInfo(const int32_t level) const;
+
+	SpellID id;
+	std::string identifier;
+	std::string modScope;
+	const std::string & getName() const override;
 public:
 	enum ESpellPositiveness
 	{
@@ -189,10 +192,6 @@ public:
 
 	using BTVector = std::vector<Bonus::BonusType>;
 
-	SpellID id;
-	std::string identifier;
-	std::string name;
-
 	si32 level;
 
 	std::map<ESpellSchool, bool> school;
@@ -237,13 +236,16 @@ public:
 
 	int32_t getIndex() const override;
 	int32_t getIconIndex() const override;
-	const std::string & getName() const override;
 	const std::string & getJsonKey() const override;
 	SpellID getId() const override;
 
-	int32_t getLevel() const override;
+	std::string getNameTextID() const override;
+	std::string getNameTranslated() const override;
 
-	const std::string & getLevelDescription(const int32_t skillLevel) const override;
+	std::string getDescriptionTextID(int32_t level) const override;
+	std::string getDescriptionTranslated(int32_t level) const override;
+
+	int32_t getLevel() const override;
 
 	boost::logic::tribool getPositiveness() const override;
 
@@ -277,7 +279,6 @@ public:
 	{
 		h & identifier;
 		h & id;
-		h & name;
 		h & level;
 		h & power;
 		h & probabilities;

+ 3 - 3
mapeditor/inspector/inspector.cpp

@@ -288,9 +288,9 @@ void Inspector::updateProperties(CGArtifact * o)
 			for(auto spell : VLC->spellh->objects)
 			{
 				//if(map->isAllowedSpell(spell->id))
-				delegate->options << QObject::tr(spell->name.c_str());
+				delegate->options << QObject::tr(spell->getJsonKey().c_str());
 			}
-			addProperty("Spell", VLC->spellh->objects[spellId]->name, delegate, false);
+			addProperty("Spell", VLC->spellh->objects[spellId]->getJsonKey(), delegate, false);
 		}
 	}
 }
@@ -524,7 +524,7 @@ void Inspector::setProperty(CGArtifact * o, const QString & key, const QVariant
 	{
 		for(auto spell : VLC->spellh->objects)
 		{
-			if(spell->name == value.toString().toStdString())
+			if(spell->getJsonKey() == value.toString().toStdString())
 			{
 				o->storedArtifact = CArtifactInstance::createScroll(spell->getId());
 				break;