Implement better in-game localization/translation

This commit is contained in:
Lyu Zong-Hong
2021-02-25 21:34:52 +09:00
parent e428e66623
commit aa1da0c652
25 changed files with 233 additions and 119 deletions

View File

@@ -1,17 +1,20 @@
package forge.util; package forge.util;
import com.google.common.base.Charsets; import com.google.common.base.Charsets;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.tuple.Pair;
import java.io.FileInputStream; import java.io.FileInputStream;
import java.io.IOException; import java.io.IOException;
import java.util.HashMap; import java.util.*;
import java.util.Map;
public class CardTranslation { public class CardTranslation {
private static Map <String, String> translatednames; private static Map <String, String> translatednames;
private static Map <String, String> translatedtypes; private static Map <String, String> translatedtypes;
private static Map <String, String> translatedoracles; private static Map <String, String> translatedoracles;
private static Map <String, List <Pair <String, String> > > oracleMappings;
private static Map <String, String> translatedCaches;
private static String languageSelected = "en-US"; private static String languageSelected = "en-US";
private static void readTranslationFile(String language, String languagesDirectory) { private static void readTranslationFile(String language, String languagesDirectory) {
@@ -27,7 +30,7 @@ public class CardTranslation {
translatedtypes.put(matches[0], matches[2]); translatedtypes.put(matches[0], matches[2]);
} }
if (matches.length >= 4) { if (matches.length >= 4) {
translatedoracles.put(matches[0], matches[3].replace("\\n", "\n\n")); translatedoracles.put(matches[0], matches[3].replace("\\n", "\r\n\r\n"));
} }
} }
} catch (IOException e) { } catch (IOException e) {
@@ -89,7 +92,101 @@ public class CardTranslation {
translatednames = new HashMap<>(); translatednames = new HashMap<>();
translatedtypes = new HashMap<>(); translatedtypes = new HashMap<>();
translatedoracles = new HashMap<>(); translatedoracles = new HashMap<>();
oracleMappings = new HashMap<>();
translatedCaches = new HashMap<>();
readTranslationFile(languageSelected, languagesDirectory); readTranslationFile(languageSelected, languagesDirectory);
} }
} }
}
private static String replaceCardName(String language, String name, String toracle) {
String nickName = language.equals("en-US") ? Lang.getEnglishInstance().getNickName(name) : Lang.getInstance().getNickName(name);
String result = TextUtil.fastReplace(toracle, name, "CARDNAME");
if (!nickName.equals(name)) {
result = TextUtil.fastReplace(result, nickName, "NICKNAME");
}
return result;
}
public static void buildOracleMapping(String faceName, String oracleText) {
if (!needsTranslation() || oracleMappings.containsKey(faceName)) return;
String translatedName = getTranslatedName(faceName);
String translatedText = getTranslatedOracle(faceName);
List <Pair <String, String> > mapping = new ArrayList<>();
String [] splitOracleText = oracleText.split("\\\\n");
String [] splitTranslatedText = translatedText.split("\r\n\r\n");
for (int i = 0; i < splitOracleText.length && i < splitTranslatedText.length; i++) {
String toracle = replaceCardName("en-US", faceName, splitOracleText[i]);
String ttranslated = replaceCardName(languageSelected, translatedName, splitTranslatedText[i]);
// Remove reminder text in English oracle text unless entire line is reminder text
if (!toracle.startsWith("(")) {
toracle = toracle.replaceAll("\\(.*\\)", "");
}
mapping.add(Pair.of(toracle, ttranslated));
}
oracleMappings.put(faceName, mapping);
}
public static String translateMultipleDescriptionText(String descText, String cardName) {
if (!needsTranslation()) return descText;
String [] splitDescText = descText.split("\r\n");
String result = descText;
for (String text : splitDescText) {
if (text.isEmpty()) continue;
String translated = translateSingleDescriptionText(text, cardName);
if (!text.equals(translated)) {
result = TextUtil.fastReplace(result, text, translated);
} else {
// keywords maybe combined into one line, split them and try translate again
String [] splitKeywords = text.split(", ");
if (splitKeywords.length <= 1) continue;
for (String keyword : splitKeywords) {
if (keyword.contains(" ")) continue;
translated = translateSingleDescriptionText(keyword, cardName);
if (!keyword.equals(translated)) {
result = TextUtil.fastReplace(result, keyword, translated);
}
}
}
}
return result;
}
public static String translateSingleDescriptionText(String descText, String cardName) {
if (!needsTranslation()) return descText;
if (translatedCaches.containsKey(descText)) return translatedCaches.get(descText);
List <Pair <String, String> > mapping = oracleMappings.get(cardName);
if (mapping == null) return descText;
String result = descText;
if (!mapping.isEmpty()) {
result = translateSingleIngameText(descText, mapping);
}
translatedCaches.put(descText, result);
return result;
}
private static String translateSingleIngameText(String descText, List <Pair <String, String> > mapping) {
String tcompare = descText.startsWith("(") ? descText : descText.replaceAll("\\(.*\\)", "");
// Use Levenshtein Distance to find matching oracle text and replace it with translated text
int candidateIndex = mapping.size();
int minDistance = tcompare.length();
for (int i = 0; i < mapping.size(); i++) {
String toracle = mapping.get(i).getLeft();
int threshold = Math.min(toracle.length(), tcompare.length()) / 3;
int distance = StringUtils.getLevenshteinDistance(toracle, tcompare, threshold);
if (distance != -1 && distance < minDistance) {
minDistance = distance;
candidateIndex = i;
}
}
if (candidateIndex < mapping.size()) {
return mapping.get(candidateIndex).getRight();
}
return descText;
}
}

View File

@@ -18,6 +18,7 @@ import com.google.common.collect.Lists;
public abstract class Lang { public abstract class Lang {
private static Lang instance; private static Lang instance;
private static Lang englishInstance;
protected String languageCode; protected String languageCode;
protected String countryCode; protected String countryCode;
@@ -41,11 +42,20 @@ public abstract class Lang {
} }
instance.languageCode = language; instance.languageCode = language;
instance.countryCode = country; instance.countryCode = country;
// Create english instance for internal usage
englishInstance = new LangEnglish();
englishInstance.languageCode = "en";
englishInstance.countryCode = "US";
} }
public static Lang getInstance() { public static Lang getInstance() {
return instance; return instance;
} }
public static Lang getEnglishInstance() {
return englishInstance;
}
protected Lang() { protected Lang() {
} }
@@ -170,4 +180,6 @@ public abstract class Lang {
} }
return Integer.toString(n); return Integer.toString(n);
} }
public abstract String getNickName(final String name);
} }

View File

@@ -19,4 +19,9 @@ public class LangChinese extends Lang {
return getPossesive(owner) + object; return getPossesive(owner) + object;
} }
@Override
public String getNickName(final String name) {
return name;
}
} }

View File

@@ -30,4 +30,8 @@ public class LangEnglish extends Lang {
return getPossesive(owner) + " " + object; return getPossesive(owner) + " " + object;
} }
@Override
public String getNickName(final String name) {
return name.split(", ")[0];
}
} }

View File

@@ -26,4 +26,10 @@ public class LangGerman extends Lang {
return getPossesive(owner) + " " + object; return getPossesive(owner) + " " + object;
} }
@Override
public String getNickName(final String name) {
return name.split(", ")[0];
}
} }

View File

@@ -23,4 +23,9 @@ public class LangItalian extends Lang {
return getPossesive(owner) + " " + object; return getPossesive(owner) + " " + object;
} }
@Override
public String getNickName(final String name) {
return name.split(", ")[0];
}
} }

View File

@@ -19,4 +19,11 @@ public class LangJapanese extends Lang {
return getPossesive(owner) + object; return getPossesive(owner) + object;
} }
@Override
public String getNickName(final String name) {
String [] splitName = name.split("");
if (splitName.length > 1) return splitName[1];
return name;
}
} }

View File

@@ -23,4 +23,9 @@ public class LangSpanish extends Lang {
return getPossesive(owner) + " " + object; return getPossesive(owner) + " " + object;
} }
@Override
public String getNickName(final String name) {
return name.split(", ")[0];
}
} }

View File

@@ -87,7 +87,7 @@ public abstract class SpellAbilityEffect {
// by typing "SpellDescription" they want to bypass the Effect's string builder // by typing "SpellDescription" they want to bypass the Effect's string builder
if ("SpellDescription".equalsIgnoreCase(stackDesc)) { if ("SpellDescription".equalsIgnoreCase(stackDesc)) {
if (params.get("SpellDescription") != null) { if (params.get("SpellDescription") != null) {
sb.append(params.get("SpellDescription")); sb.append(CardTranslation.translateSingleDescriptionText(params.get("SpellDescription"), sa.getHostCard().getName()));
} }
if (sa.getTargets() != null && !sa.getTargets().isEmpty()) { if (sa.getTargets() != null && !sa.getTargets().isEmpty()) {
sb.append(" (Targeting: ").append(sa.getTargets()).append(")"); sb.append(" (Targeting: ").append(sa.getTargets()).append(")");
@@ -98,7 +98,7 @@ public abstract class SpellAbilityEffect {
} else { } else {
final String conditionDesc = sa.getParam("ConditionDescription"); final String conditionDesc = sa.getParam("ConditionDescription");
final String afterDesc = sa.getParam("AfterDescription"); final String afterDesc = sa.getParam("AfterDescription");
final String baseDesc = this.getStackDescription(sa); final String baseDesc = CardTranslation.translateSingleDescriptionText(this.getStackDescription(sa), sa.getHostCard().getName());
if (conditionDesc != null) { if (conditionDesc != null) {
sb.append(conditionDesc).append(" "); sb.append(conditionDesc).append(" ");
} }
@@ -131,8 +131,8 @@ public abstract class SpellAbilityEffect {
} }
String currentName = (sa.getHostCard().getName()); String currentName = (sa.getHostCard().getName());
String substitutedDesc = TextUtil.fastReplace(sb.toString(), "CARDNAME", currentName); String substitutedDesc = TextUtil.fastReplace(sb.toString(), "CARDNAME", CardTranslation.getTranslatedName(currentName));
substitutedDesc = TextUtil.fastReplace(substitutedDesc, "NICKNAME", currentName.split(",")[0]); substitutedDesc = TextUtil.fastReplace(substitutedDesc, "NICKNAME", Lang.getInstance().getNickName(CardTranslation.getTranslatedName(currentName)));
return substitutedDesc; return substitutedDesc;
} }

View File

@@ -2,6 +2,7 @@ package forge.game.ability.effects;
import forge.game.card.Card; import forge.game.card.Card;
import forge.game.spellability.SpellAbility; import forge.game.spellability.SpellAbility;
import forge.util.CardTranslation;
/** /**
* TODO: Write javadoc for this type. * TODO: Write javadoc for this type.
@@ -13,6 +14,6 @@ public class PermanentNoncreatureEffect extends PermanentEffect {
public String getStackDescription(final SpellAbility sa) { public String getStackDescription(final SpellAbility sa) {
final Card sourceCard = sa.getHostCard(); final Card sourceCard = sa.getHostCard();
//CardView toString return translated name,don't need call CardTranslation.getTranslatedName in this. //CardView toString return translated name,don't need call CardTranslation.getTranslatedName in this.
return sourceCard.getName(); return CardTranslation.getTranslatedName(sourceCard.getName());
} }
} }

View File

@@ -2081,7 +2081,7 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars {
sbLong.append("\r\n"); sbLong.append("\r\n");
} }
sb.append(sbLong); sb.append(sbLong);
return sb.toString(); return CardTranslation.translateMultipleDescriptionText(sb.toString(), getName());
} }
private static String getTextForKwCantBeBlockedByAmount(final String keyword) { private static String getTextForKwCantBeBlockedByAmount(final String keyword) {
@@ -2119,7 +2119,7 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars {
while (result.endsWith("\r\n")) { while (result.endsWith("\r\n")) {
result = result.substring(0, result.length() - 2); result = result.substring(0, result.length() - 2);
} }
return TextUtil.fastReplace(result, "CARDNAME", state.getName()); return TextUtil.fastReplace(result, "CARDNAME", CardTranslation.getTranslatedName(state.getName()));
} }
if (monstrous) { if (monstrous) {
@@ -2140,7 +2140,9 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars {
for (final ReplacementEffect replacementEffect : state.getReplacementEffects()) { for (final ReplacementEffect replacementEffect : state.getReplacementEffects()) {
if (!replacementEffect.isSecondary()) { if (!replacementEffect.isSecondary()) {
String text = replacementEffect.getDescription(); String text = replacementEffect.getDescription();
if (text.contains("enters the battlefield")) { // Get original description since text might be translated
if (replacementEffect.hasParam("Description") &&
replacementEffect.getParam("Description").contains("enters the battlefield")) {
sb.append(text).append("\r\n"); sb.append(text).append("\r\n");
} else { } else {
replacementEffects.append(text).append("\r\n"); replacementEffects.append(text).append("\r\n");
@@ -2226,8 +2228,10 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars {
// add Adventure to AbilityText // add Adventure to AbilityText
if (sa.isAdventure() && state.getView().getState().equals(CardStateName.Original)) { if (sa.isAdventure() && state.getView().getState().equals(CardStateName.Original)) {
CardState advState = getState(CardStateName.Adventure);
StringBuilder sbSA = new StringBuilder(); StringBuilder sbSA = new StringBuilder();
sbSA.append("Adventure — ").append(getState(CardStateName.Adventure).getName()); sbSA.append(Localizer.getInstance().getMessage("lblAdventure"));
sbSA.append("").append(CardTranslation.getTranslatedName(advState.getName()));
sbSA.append(" ").append(sa.getPayCosts().toSimpleString()); sbSA.append(" ").append(sa.getPayCosts().toSimpleString());
sbSA.append(": "); sbSA.append(": ");
sbSA.append(sAbility); sbSA.append(sAbility);
@@ -2299,7 +2303,7 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars {
start = sb.lastIndexOf(s); start = sb.lastIndexOf(s);
} }
String desc = TextUtil.fastReplace(sb.toString(), "CARDNAME", state.getName()); String desc = TextUtil.fastReplace(sb.toString(), "CARDNAME", CardTranslation.getTranslatedName(state.getName()));
if (getEffectSource() != null) { if (getEffectSource() != null) {
desc = TextUtil.fastReplace(desc, "EFFECTSOURCE", getEffectSource().getName()); desc = TextUtil.fastReplace(desc, "EFFECTSOURCE", getEffectSource().getName());
} }
@@ -2485,7 +2489,7 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars {
} }
} }
sb.append(sbBefore); sb.append(CardTranslation.translateMultipleDescriptionText(sbBefore.toString(), state.getName()));
// add Spells there to main StringBuilder // add Spells there to main StringBuilder
sb.append(strSpell); sb.append(strSpell);
@@ -2514,13 +2518,13 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars {
} }
} }
sb.append(sbAfter); sb.append(CardTranslation.translateMultipleDescriptionText(sbAfter.toString(), state.getName()));
return sb; return sb;
} }
private String formatSpellAbility(final SpellAbility sa) { private String formatSpellAbility(final SpellAbility sa) {
final StringBuilder sb = new StringBuilder(); final StringBuilder sb = new StringBuilder();
sb.append(sa.toString()).append("\r\n"); sb.append(sa.toString()).append("\r\n\r\n");
return sb.toString(); return sb.toString();
} }

View File

@@ -38,6 +38,7 @@ import forge.game.trigger.TriggerHandler;
import forge.game.trigger.WrappedAbility; import forge.game.trigger.WrappedAbility;
import forge.item.IPaperCard; import forge.item.IPaperCard;
import forge.item.PaperCard; import forge.item.PaperCard;
import forge.util.CardTranslation;
import forge.util.TextUtil; import forge.util.TextUtil;
import java.util.Arrays; import java.util.Arrays;
@@ -354,6 +355,10 @@ public class CardFactory {
} }
private static void readCardFace(Card c, ICardFace face) { private static void readCardFace(Card c, ICardFace face) {
// Build English oracle and translated oracle mapping
if (c.getId() >= 0) {
CardTranslation.buildOracleMapping(face.getName(), face.getOracleText());
}
// Name first so Senty has the Card name // Name first so Senty has the Card name
c.setName(face.getName()); c.setName(face.getName());

View File

@@ -591,14 +591,8 @@ public class CardView extends GameEntityView {
if (translationsText != null) { if (translationsText != null) {
tname = translationsText.get("name"); tname = translationsText.get("name");
taltname = translationsText.get("altname"); taltname = translationsText.get("altname");
toracle = translationsText.get("oracle");
// TODO: Translate for cloned or mutated cards taltoracle = translationsText.get("altoracle");
// For now, don't translate oracles if the card is a cloned or mutated
if (((String) get(TrackableProperty.Cloner)).isEmpty() &&
((String) get(TrackableProperty.MergedCards)).isEmpty()) {
toracle = translationsText.get("oracle");
taltoracle = translationsText.get("altoracle");
}
} }
if (isSplitCard()) { if (isSplitCard()) {
@@ -629,18 +623,7 @@ public class CardView extends GameEntityView {
} }
final String rulesText = state.getRulesText(); final String rulesText = state.getRulesText();
if (!toracle.isEmpty() && !isFaceDown()) { if (!rulesText.isEmpty()) {
if (isSplitCard()) {
sb.append("(").append(tname).append(") ");
sb.append(toracle);
sb.append("\r\n\r\n");
sb.append("(").append(taltname).append(") ");
sb.append(taltoracle);
} else {
sb.append(toracle);
}
sb.append("\r\n\r\n");
} else if (!rulesText.isEmpty()) {
sb.append(rulesText).append("\r\n\r\n"); sb.append(rulesText).append("\r\n\r\n");
} }
if (isCommander()) { if (isCommander()) {
@@ -648,15 +631,13 @@ public class CardView extends GameEntityView {
sb.append(getOwner().getCommanderInfo(this)).append("\r\n"); sb.append(getOwner().getCommanderInfo(this)).append("\r\n");
} }
if (toracle.isEmpty()) { if (isSplitCard() && !isFaceDown() && getZone() != ZoneType.Stack) {
if (isSplitCard() && !isFaceDown() && getZone() != ZoneType.Stack) { sb.append("(").append(getLeftSplitState().getName()).append(") ");
sb.append("(").append(getLeftSplitState().getName()).append(") "); sb.append(getLeftSplitState().getAbilityText());
sb.append(getLeftSplitState().getAbilityText()); sb.append("\r\n\r\n").append("(").append(getRightSplitState().getName()).append(") ");
sb.append("\r\n\r\n").append("(").append(getRightSplitState().getName()).append(") "); sb.append(getRightSplitState().getAbilityText());
sb.append(getRightSplitState().getAbilityText()); } else {
} else { sb.append(state.getAbilityText());
sb.append(state.getAbilityText());
}
} }
String nonAbilityText = get(TrackableProperty.NonAbilityText); String nonAbilityText = get(TrackableProperty.NonAbilityText);
@@ -1093,7 +1074,7 @@ public class CardView extends GameEntityView {
return get(TrackableProperty.OracleText); return get(TrackableProperty.OracleText);
} }
void updateOracleText(Card c) { void updateOracleText(Card c) {
set(TrackableProperty.OracleText, c.getOracleText().replace("\\n", "\r\n").trim()); set(TrackableProperty.OracleText, c.getOracleText().replace("\\n", "\r\n\r\n").trim());
} }
public String getRulesText() { public String getRulesText() {

View File

@@ -27,7 +27,6 @@ import forge.game.spellability.SpellAbility;
import forge.game.zone.ZoneType; import forge.game.zone.ZoneType;
import forge.util.TextUtil; import forge.util.TextUtil;
import forge.util.collect.FCollectionView; import forge.util.collect.FCollectionView;
import forge.util.Localizer;
/** /**
* The Class CostExile. * The Class CostExile.
@@ -89,32 +88,32 @@ public class CostExile extends CostPartWithList {
if (this.payCostFromSource()) { if (this.payCostFromSource()) {
if (!this.from.equals(ZoneType.Battlefield)) { if (!this.from.equals(ZoneType.Battlefield)) {
return Localizer.getInstance().getMessage("lblExileTargetsFromYourZone", this.getType(), this.from.getTranslatedName()); return String.format("Exile %s from your %s", this.getType(), this.from.name());
} }
return Localizer.getInstance().getMessage("lblExileTarget", this.getType()); return String.format("Exile %s", this.getType());
} else if (this.getType().equals("All")) { } else if (this.getType().equals("All")) {
return Localizer.getInstance().getMessage("lblExileAllCardsFromYourZone", this.from.getTranslatedName()); return String.format("Exile all cards from your %s", this.from.name());
} }
if (this.from.equals(ZoneType.Battlefield)) { if (this.from.equals(ZoneType.Battlefield)) {
if (!this.payCostFromSource()) { if (!this.payCostFromSource()) {
return Localizer.getInstance().getMessage("lblExileTargetsYourControl", Cost.convertAmountTypeToWords(i, this.getAmount(), desc)); return String.format("Exile %s you control", Cost.convertAmountTypeToWords(i, this.getAmount(), desc));
} }
return Localizer.getInstance().getMessage("lblExileTarget", Cost.convertAmountTypeToWords(i, this.getAmount(), desc)); return String.format("Exile %s", Cost.convertAmountTypeToWords(i, this.getAmount(), desc));
} }
if (!desc.equals("Card") && !desc.endsWith("card")) { if (!desc.equals("Card") && !desc.endsWith("card")) {
if (this.sameZone) { if (this.sameZone) {
return Localizer.getInstance().getMessage("lblExileNCardFromSameZone", Cost.convertAmountTypeToWords(i, this.getAmount(), desc), this.from.getTranslatedName()); return String.format("Exile card %s from the same %s", Cost.convertAmountTypeToWords(i, this.getAmount(), desc), this.from.name());
} }
return Localizer.getInstance().getMessage("lblExileNCardFromYourZone", Cost.convertAmountTypeToWords(i, this.getAmount(), desc), this.from.getTranslatedName()); return String.format("Exile card %s from your %s", Cost.convertAmountTypeToWords(i, this.getAmount(), desc), this.from.name());
} }
if (this.sameZone) { if (this.sameZone) {
return Localizer.getInstance().getMessage("lblExileNTargetFromSameZone", Cost.convertAmountTypeToWords(i, this.getAmount(), desc), this.from.getTranslatedName()); return String.format("Exile %s from the same %s", Cost.convertAmountTypeToWords(i, this.getAmount(), desc), this.from.name());
} }
return Localizer.getInstance().getMessage("lblExileTargetsFromYourZone", Cost.convertAmountTypeToWords(i, this.getAmount(), desc), this.from.getTranslatedName()); return String.format("Exile %s from your %s", Cost.convertAmountTypeToWords(i, this.getAmount(), desc), this.from.name());
} }
@Override @Override

View File

@@ -25,6 +25,8 @@ import forge.game.ability.AbilityUtils;
import forge.game.card.Card; import forge.game.card.Card;
import forge.game.phase.PhaseType; import forge.game.phase.PhaseType;
import forge.game.spellability.SpellAbility; import forge.game.spellability.SpellAbility;
import forge.util.CardTranslation;
import forge.util.Lang;
import forge.util.TextUtil; import forge.util.TextUtil;
import java.util.List; import java.util.List;
@@ -214,15 +216,13 @@ public abstract class ReplacementEffect extends TriggerReplacementBase {
public String getDescription() { public String getDescription() {
if (hasParam("Description") && !this.isSuppressed()) { if (hasParam("Description") && !this.isSuppressed()) {
String desc = AbilityUtils.applyDescriptionTextChangeEffects(getParam("Description"), this); String desc = AbilityUtils.applyDescriptionTextChangeEffects(getParam("Description"), this);
if (desc.contains("CARDNAME")) { String currentName = getHostCard().getName();
desc = TextUtil.fastReplace(desc, "CARDNAME", getHostCard().toString()); desc = CardTranslation.translateSingleDescriptionText(desc, currentName);
} desc = TextUtil.fastReplace(desc, "CARDNAME", CardTranslation.getTranslatedName(currentName));
desc = TextUtil.fastReplace(desc, "NICKNAME", Lang.getInstance().getNickName(CardTranslation.getTranslatedName(currentName)));
if (desc.contains("EFFECTSOURCE")) { if (desc.contains("EFFECTSOURCE")) {
desc = TextUtil.fastReplace(desc, "EFFECTSOURCE", getHostCard().getEffectSource().toString()); desc = TextUtil.fastReplace(desc, "EFFECTSOURCE", getHostCard().getEffectSource().toString());
} }
if (desc.contains("NICKNAME")) {
desc = TextUtil.fastReplace(desc, "NICKNAME", getHostCard().toString().split(",")[0]);
}
return desc; return desc;
} else { } else {
return ""; return "";

View File

@@ -54,7 +54,9 @@ import forge.game.trigger.TriggerType;
import forge.game.trigger.WrappedAbility; import forge.game.trigger.WrappedAbility;
import forge.game.zone.ZoneType; import forge.game.zone.ZoneType;
import forge.util.Aggregates; import forge.util.Aggregates;
import forge.util.CardTranslation;
import forge.util.Expressions; import forge.util.Expressions;
import forge.util.Lang;
import forge.util.TextUtil; import forge.util.TextUtil;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
@@ -822,8 +824,10 @@ public abstract class SpellAbility extends CardTraitBase implements ISpellAbilit
} }
String desc = node.getDescription(); String desc = node.getDescription();
if (node.getHostCard() != null) { if (node.getHostCard() != null) {
desc = TextUtil.fastReplace(desc, "CARDNAME", node.getHostCard().getName()); String currentName = node.getHostCard().getName();
desc = TextUtil.fastReplace(desc,"NICKNAME",node.getHostCard().getName().split(",")[0]); desc = CardTranslation.translateMultipleDescriptionText(desc, currentName);
desc = TextUtil.fastReplace(desc, "CARDNAME", CardTranslation.getTranslatedName(currentName));
desc = TextUtil.fastReplace(desc, "NICKNAME", Lang.getInstance().getNickName(CardTranslation.getTranslatedName(currentName)));
if (node.getOriginalHost() != null) { if (node.getOriginalHost() != null) {
desc = TextUtil.fastReplace(desc, "ORIGINALHOST", node.getOriginalHost().getName()); desc = TextUtil.fastReplace(desc, "ORIGINALHOST", node.getOriginalHost().getName());
} }

View File

@@ -39,7 +39,9 @@ import forge.game.player.Player;
import forge.game.spellability.SpellAbility; import forge.game.spellability.SpellAbility;
import forge.game.zone.Zone; import forge.game.zone.Zone;
import forge.game.zone.ZoneType; import forge.game.zone.ZoneType;
import forge.util.CardTranslation;
import forge.util.Expressions; import forge.util.Expressions;
import forge.util.Lang;
import forge.util.TextUtil; import forge.util.TextUtil;
import java.util.EnumSet; import java.util.EnumSet;
@@ -214,9 +216,10 @@ public class StaticAbility extends CardTraitBase implements IIdentifiable, Clone
@Override @Override
public final String toString() { public final String toString() {
if (hasParam("Description") && !this.isSuppressed()) { if (hasParam("Description") && !this.isSuppressed()) {
String desc = getParam("Description"); String currentName = this.hostCard.getName();
desc = TextUtil.fastReplace(desc, "CARDNAME", this.hostCard.getName()); String desc = CardTranslation.translateSingleDescriptionText(getParam("Description"), currentName);
desc = TextUtil.fastReplace(desc, "NICKNAME", this.hostCard.getName().split(",")[0]); desc = TextUtil.fastReplace(desc, "CARDNAME", CardTranslation.getTranslatedName(currentName));
desc = TextUtil.fastReplace(desc, "NICKNAME", Lang.getInstance().getNickName(CardTranslation.getTranslatedName(currentName)));
return desc; return desc;
} else { } else {

View File

@@ -39,6 +39,8 @@ import java.util.*;
import com.google.common.collect.Lists; import com.google.common.collect.Lists;
import com.google.common.collect.Sets; import com.google.common.collect.Sets;
import forge.util.CardTranslation;
import forge.util.Lang;
import forge.util.TextUtil; import forge.util.TextUtil;
/** /**
@@ -124,9 +126,13 @@ public abstract class Trigger extends TriggerReplacementBase {
if (hasParam("TriggerDescription") && !this.isSuppressed()) { if (hasParam("TriggerDescription") && !this.isSuppressed()) {
StringBuilder sb = new StringBuilder(); StringBuilder sb = new StringBuilder();
String currentName = (getHostCard().getName()); String currentName = getHostCard().getName();
String desc1 = TextUtil.fastReplace(getParam("TriggerDescription"),"CARDNAME", currentName); String desc = getParam("TriggerDescription");
String desc = TextUtil.fastReplace(desc1,"NICKNAME", currentName.split(",")[0]); if (!desc.contains("ABILITY")) {
desc = CardTranslation.translateSingleDescriptionText(getParam("TriggerDescription"), currentName);
desc = TextUtil.fastReplace(desc,"CARDNAME", CardTranslation.getTranslatedName(currentName));
desc = TextUtil.fastReplace(desc,"NICKNAME", Lang.getInstance().getNickName(CardTranslation.getTranslatedName(currentName)));
}
if (getHostCard().getEffectSource() != null) { if (getHostCard().getEffectSource() != null) {
if(active) if(active)
desc = TextUtil.fastReplace(desc, "EFFECTSOURCE", getHostCard().getEffectSource().toString()); desc = TextUtil.fastReplace(desc, "EFFECTSOURCE", getHostCard().getEffectSource().toString());
@@ -198,6 +204,11 @@ public abstract class Trigger extends TriggerReplacementBase {
saDesc = "<take no action>"; // printed in case nothing is chosen for the ability (e.g. Charm with Up to X) saDesc = "<take no action>"; // printed in case nothing is chosen for the ability (e.g. Charm with Up to X)
} }
result = TextUtil.fastReplace(result, "ABILITY", saDesc); result = TextUtil.fastReplace(result, "ABILITY", saDesc);
String currentName = sa.getHostCard().getName();
result = CardTranslation.translateMultipleDescriptionText(result, currentName);
result = TextUtil.fastReplace(result,"CARDNAME", CardTranslation.getTranslatedName(currentName));
result = TextUtil.fastReplace(result,"NICKNAME", Lang.getInstance().getNickName(CardTranslation.getTranslatedName(currentName)));
} }
return result; return result;

View File

@@ -43,6 +43,7 @@ public class SimulationTestCase extends TestCase {
@Override @Override
public Void apply(ForgePreferences preferences) { public Void apply(ForgePreferences preferences) {
preferences.setPref(FPref.LOAD_CARD_SCRIPTS_LAZILY, false); preferences.setPref(FPref.LOAD_CARD_SCRIPTS_LAZILY, false);
preferences.setPref(FPref.UI_LANGUAGE, "en-US");
return null; return null;
} }
}); });

View File

@@ -1417,6 +1417,8 @@ lblOtherFormats=Andere Formate...
lblChooseSets=Wähle Sets... lblChooseSets=Wähle Sets...
#HistoricFormatSelect.java #HistoricFormatSelect.java
lblChooseFormat=Wähle Format lblChooseFormat=Wähle Format
#Card.java
lblAdventure=Adventure
#TriggerAdapt.java #TriggerAdapt.java
lblAdapt=Adaptieren lblAdapt=Adaptieren
#TriggerAttached.java #TriggerAttached.java
@@ -2286,14 +2288,6 @@ lblCostPaymentInvalid=Bezahlung der Kosten unmöglich
lblSelectATargetToTap=Wähle ein(e) {0} zum Tappen (noch {1}) lblSelectATargetToTap=Wähle ein(e) {0} zum Tappen (noch {1})
lblSelectATargetToUntap=Wähle ein(e) {0} zum Enttappen (noch {1}) lblSelectATargetToUntap=Wähle ein(e) {0} zum Enttappen (noch {1})
lblUnattachCardConfirm=Löse {0}? lblUnattachCardConfirm=Löse {0}?
#CostExile.java
lblExileTargetsFromYourZone=Schicke {0} von deiner/deinem {1} ins Exil
lblExileTarget=Schicke {0} ins Exil
lblExileAllCardsFromYourZone=Schicke alle Karten aus deiner/deinem {0} ins Exil
lblExileTargetsYourControl=Schicke {0}, welche du kontrollierst, ins Exil
lblExileNCardFromSameZone=Schicke Karte {0} der/dem gleichen {1} ins Exil
lblExileNCardFromYourZone=Schicke Karte {0} von deiner/deinem {1} ins Exil
lblExileNTargetFromSameZone=Schicke {0} der/dem gleichen {1} ins Exil
#ReplacementHandler.java #ReplacementHandler.java
lblChooseFirstApplyReplacementEffect=Welchen Ersatzeffekt zuerst anwenden? lblChooseFirstApplyReplacementEffect=Welchen Ersatzeffekt zuerst anwenden?
lblApplyCardReplacementEffectToCardConfirm=Wende Ersatzeffekt von {0} auf {1} an?\r\n{2} lblApplyCardReplacementEffectToCardConfirm=Wende Ersatzeffekt von {0} auf {1} an?\r\n{2}

View File

@@ -1417,6 +1417,8 @@ lblOtherFormats=Other Formats
lblChooseSets=Choose Sets lblChooseSets=Choose Sets
#HistoricFormatSelect.java #HistoricFormatSelect.java
lblChooseFormat=Choose Format lblChooseFormat=Choose Format
#Card.java
lblAdventure=Adventure
#TriggerAdapt.java #TriggerAdapt.java
lblAdapt=Adapt lblAdapt=Adapt
#TriggerAttached.java #TriggerAttached.java
@@ -2285,14 +2287,6 @@ lblCostPaymentInvalid=Cost payment invalid
lblSelectATargetToTap=Select a {0} to tap ({1} left) lblSelectATargetToTap=Select a {0} to tap ({1} left)
lblSelectATargetToUntap=Select a {0} to untap ({1} left) lblSelectATargetToUntap=Select a {0} to untap ({1} left)
lblUnattachCardConfirm=Unattach {0}? lblUnattachCardConfirm=Unattach {0}?
#CostExile.java
lblExileTargetsFromYourZone=Exile {0} from your {1}
lblExileTarget=Exile {0}
lblExileAllCardsFromYourZone=Exile all cards from your {0}
lblExileTargetsYourControl=Exile {0} you control
lblExileNCardFromSameZone=Exile card {0} from the same {1}
lblExileNCardFromYourZone=Exile card {0} from your {1}
lblExileNTargetFromSameZone=Exile {0} from the same {1}
#ReplacementHandler.java #ReplacementHandler.java
lblChooseFirstApplyReplacementEffect=Choose a replacement effect to apply first. lblChooseFirstApplyReplacementEffect=Choose a replacement effect to apply first.
lblApplyCardReplacementEffectToCardConfirm=Apply replacement effect of {0} to {1}?\r\n{2} lblApplyCardReplacementEffectToCardConfirm=Apply replacement effect of {0} to {1}?\r\n{2}

View File

@@ -1417,6 +1417,8 @@ lblOtherFormats=Otros formatos...
lblChooseSets=Selecciona ediciones... lblChooseSets=Selecciona ediciones...
#HistoricFormatSelect.java #HistoricFormatSelect.java
lblChooseFormat=Selecciona formato lblChooseFormat=Selecciona formato
#Card.java
lblAdventure=Adventure
#TriggerAdapt.java #TriggerAdapt.java
lblAdapt=Adaptar lblAdapt=Adaptar
#TriggerAttached.java #TriggerAttached.java
@@ -2284,14 +2286,6 @@ lblCostPaymentInvalid=Pago del coste no válido
lblSelectATargetToTap=Selecciona un/a {0} para girar ({1} pendiente) lblSelectATargetToTap=Selecciona un/a {0} para girar ({1} pendiente)
lblSelectATargetToUntap=Selecciona un/a {0} para enderezar ({1} pendiente) lblSelectATargetToUntap=Selecciona un/a {0} para enderezar ({1} pendiente)
lblUnattachCardConfirm=¿Desanexar {0}? lblUnattachCardConfirm=¿Desanexar {0}?
#CostExile.java
lblExileTargetsFromYourZone=Exiliar {0} de tu {1}
lblExileTarget=Exiliar {0}
lblExileAllCardsFromYourZone=Exilia todas las cartas de tu {0}
lblExileTargetsYourControl=Exilia {0} que controles
lblExileNCardFromSameZone=Exilia la carta {0} del mismo {1}
lblExileNCardFromYourZone=Exilia la carta {0} de tu {1}
lblExileNTargetFromSameZone=Exilia {0} del mismo {1}
#ReplacementHandler.java #ReplacementHandler.java
lblChooseFirstApplyReplacementEffect=Elige un efecto de reemplazo para aplicar primero. lblChooseFirstApplyReplacementEffect=Elige un efecto de reemplazo para aplicar primero.
lblApplyCardReplacementEffectToCardConfirm=¿Aplicar efecto de reemplazo de {0} a {1}?\r\n{2} lblApplyCardReplacementEffectToCardConfirm=¿Aplicar efecto de reemplazo de {0} a {1}?\r\n{2}

View File

@@ -1417,6 +1417,8 @@ lblOtherFormats=Other Formats...
lblChooseSets=Choose Sets... lblChooseSets=Choose Sets...
#HistoricFormatSelect.java #HistoricFormatSelect.java
lblChooseFormat=Choose Format lblChooseFormat=Choose Format
#Card.java
lblAdventure=Adventure
#TriggerAdapt.java #TriggerAdapt.java
lblAdapt=Adapt lblAdapt=Adapt
#TriggerAttached.java #TriggerAttached.java
@@ -2286,14 +2288,6 @@ lblCostPaymentInvalid=Cost payment invalid
lblSelectATargetToTap=Select a {0} to tap ({1} left) lblSelectATargetToTap=Select a {0} to tap ({1} left)
lblSelectATargetToUntap=Select a {0} to untap ({1} left) lblSelectATargetToUntap=Select a {0} to untap ({1} left)
lblUnattachCardConfirm=Unattach {0}? lblUnattachCardConfirm=Unattach {0}?
#CostExile.java
lblExileTargetsFromYourZone=Exile {0} from your {1}
lblExileTarget=Exile {0}
lblExileAllCardsFromYourZone=Exile all cards from your {0}
lblExileTargetsYourControl=Exile {0} you control
lblExileNCardFromSameZone=Exile card {0} from the same {1}
lblExileNCardFromYourZone=Exile card {0} from your {1}
lblExileNTargetFromSameZone=Exile {0} from the same {1}
#ReplacementHandler.java #ReplacementHandler.java
lblChooseFirstApplyReplacementEffect=Choose a replacement effect to apply first. lblChooseFirstApplyReplacementEffect=Choose a replacement effect to apply first.
lblApplyCardReplacementEffectToCardConfirm=Apply replacement effect of {0} to {1}?\r\n{2} lblApplyCardReplacementEffectToCardConfirm=Apply replacement effect of {0} to {1}?\r\n{2}

View File

@@ -1417,6 +1417,8 @@ lblOtherFormats=他のフォーマット...
lblChooseSets=セットを選択 lblChooseSets=セットを選択
#HistoricFormatSelect.java #HistoricFormatSelect.java
lblChooseFormat=フォーマットを選択 lblChooseFormat=フォーマットを選択
#Card.java
lblAdventure=出来事
#TriggerAdapt.java #TriggerAdapt.java
lblAdapt=順応 lblAdapt=順応
#TriggerAttached.java #TriggerAttached.java
@@ -2285,14 +2287,6 @@ lblCostPaymentInvalid=無効な支払い
lblSelectATargetToTap=タップする {0}を選択(残り{1} lblSelectATargetToTap=タップする {0}を選択(残り{1}
lblSelectATargetToUntap=アンタップする {0}を選択(残り{1} lblSelectATargetToUntap=アンタップする {0}を選択(残り{1}
lblUnattachCardConfirm={0}を外しますか? lblUnattachCardConfirm={0}を外しますか?
#CostExile.java
lblExileTargetsFromYourZone=あなたの {1}から {0}を追放
lblExileTarget={0}を追放
lblExileAllCardsFromYourZone=あなたの {0}からすべてのカードを追放
lblExileTargetsYourControl=あなたがコントロールする {0}を追放
lblExileNCardFromSameZone=同じ {1}から {0}カードを追放
lblExileNCardFromYourZone=あなたの {1}から {0}カードを追放
lblExileNTargetFromSameZone=同じ {1}から {0}を追放
#ReplacementHandler.java #ReplacementHandler.java
lblChooseFirstApplyReplacementEffect=最初に適用する置換効果を選択してください。 lblChooseFirstApplyReplacementEffect=最初に適用する置換効果を選択してください。
lblApplyCardReplacementEffectToCardConfirm={0}の置換効果を{1}に適用しますか?\r\n{2} lblApplyCardReplacementEffectToCardConfirm={0}の置換効果を{1}に適用しますか?\r\n{2}

View File

@@ -1415,6 +1415,8 @@ lblOtherFormats=其他赛制
lblChooseSets=选择系列 lblChooseSets=选择系列
#HistoricFormatSelect.java #HistoricFormatSelect.java
lblChooseFormat=选择赛制 lblChooseFormat=选择赛制
#Card.java
lblAdventure=Adventure
#TriggerAdapt.java #TriggerAdapt.java
lblAdapt=演化 lblAdapt=演化
#TriggerAttached.java #TriggerAttached.java
@@ -2283,14 +2285,6 @@ lblCostPaymentInvalid=付费失败
lblSelectATargetToTap=选择{0}进行横置(还剩{1} lblSelectATargetToTap=选择{0}进行横置(还剩{1}
lblSelectATargetToUntap=选择{0}进行重置(还剩{1} lblSelectATargetToUntap=选择{0}进行重置(还剩{1}
lblUnattachCardConfirm=取消结附{0} lblUnattachCardConfirm=取消结附{0}
#CostExile.java
lblExileTargetsFromYourZone=从你的{1}放逐{0}
lblExileTarget=放逐{0}
lblExileAllCardsFromYourZone=从你的{0}中放逐所有牌
lblExileTargetsYourControl=放逐你操控的{0}
lblExileNCardFromSameZone=从同一{1}中的卡牌中放逐{0}张牌
lblExileNCardFromYourZone=从你的{1}中放逐{0}张牌
lblExileNTargetFromSameZone=从同一{1}中的卡牌中放逐{0}
#ReplacementHandler.java #ReplacementHandler.java
lblChooseFirstApplyReplacementEffect=选择首先生效的替代性效应。 lblChooseFirstApplyReplacementEffect=选择首先生效的替代性效应。
lblApplyCardReplacementEffectToCardConfirm=对{1}应用替代性效应{0}\r\n{2} lblApplyCardReplacementEffectToCardConfirm=对{1}应用替代性效应{0}\r\n{2}