From aa1da0c6529016de918751da83a15a029ff3eee7 Mon Sep 17 00:00:00 2001 From: Lyu Zong-Hong Date: Thu, 25 Feb 2021 21:34:52 +0900 Subject: [PATCH] Implement better in-game localization/translation --- .../main/java/forge/util/CardTranslation.java | 105 +++++++++++++++++- forge-core/src/main/java/forge/util/Lang.java | 12 ++ .../java/forge/util/lang/LangChinese.java | 5 + .../java/forge/util/lang/LangEnglish.java | 4 + .../main/java/forge/util/lang/LangGerman.java | 6 + .../java/forge/util/lang/LangItalian.java | 5 + .../java/forge/util/lang/LangJapanese.java | 7 ++ .../java/forge/util/lang/LangSpanish.java | 5 + .../game/ability/SpellAbilityEffect.java | 8 +- .../effects/PermanentNoncreatureEffect.java | 3 +- .../src/main/java/forge/game/card/Card.java | 20 ++-- .../java/forge/game/card/CardFactory.java | 5 + .../main/java/forge/game/card/CardView.java | 41 ++----- .../main/java/forge/game/cost/CostExile.java | 19 ++-- .../game/replacement/ReplacementEffect.java | 12 +- .../forge/game/spellability/SpellAbility.java | 8 +- .../game/staticability/StaticAbility.java | 9 +- .../main/java/forge/game/trigger/Trigger.java | 17 ++- .../ai/simulation/SimulationTestCase.java | 1 + forge-gui/res/languages/de-DE.properties | 10 +- forge-gui/res/languages/en-US.properties | 10 +- forge-gui/res/languages/es-ES.properties | 10 +- forge-gui/res/languages/it-IT.properties | 10 +- forge-gui/res/languages/ja-JP.properties | 10 +- forge-gui/res/languages/zh-CN.properties | 10 +- 25 files changed, 233 insertions(+), 119 deletions(-) diff --git a/forge-core/src/main/java/forge/util/CardTranslation.java b/forge-core/src/main/java/forge/util/CardTranslation.java index f33888d82d9..c4423cec5fa 100644 --- a/forge-core/src/main/java/forge/util/CardTranslation.java +++ b/forge-core/src/main/java/forge/util/CardTranslation.java @@ -1,17 +1,20 @@ package forge.util; 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.IOException; -import java.util.HashMap; -import java.util.Map; +import java.util.*; public class CardTranslation { private static Map translatednames; private static Map translatedtypes; private static Map translatedoracles; + private static Map > > oracleMappings; + private static Map translatedCaches; private static String languageSelected = "en-US"; private static void readTranslationFile(String language, String languagesDirectory) { @@ -27,7 +30,7 @@ public class CardTranslation { translatedtypes.put(matches[0], matches[2]); } 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) { @@ -89,7 +92,101 @@ public class CardTranslation { translatednames = new HashMap<>(); translatedtypes = new HashMap<>(); translatedoracles = new HashMap<>(); + oracleMappings = new HashMap<>(); + translatedCaches = new HashMap<>(); readTranslationFile(languageSelected, languagesDirectory); } } -} \ No newline at end of file + + 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 > 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 > 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 > 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; + } + +} diff --git a/forge-core/src/main/java/forge/util/Lang.java b/forge-core/src/main/java/forge/util/Lang.java index e02ce60fdaf..508f3adbac5 100644 --- a/forge-core/src/main/java/forge/util/Lang.java +++ b/forge-core/src/main/java/forge/util/Lang.java @@ -18,6 +18,7 @@ import com.google.common.collect.Lists; public abstract class Lang { private static Lang instance; + private static Lang englishInstance; protected String languageCode; protected String countryCode; @@ -41,11 +42,20 @@ public abstract class Lang { } instance.languageCode = language; instance.countryCode = country; + + // Create english instance for internal usage + englishInstance = new LangEnglish(); + englishInstance.languageCode = "en"; + englishInstance.countryCode = "US"; } public static Lang getInstance() { return instance; } + + public static Lang getEnglishInstance() { + return englishInstance; + } protected Lang() { } @@ -170,4 +180,6 @@ public abstract class Lang { } return Integer.toString(n); } + + public abstract String getNickName(final String name); } diff --git a/forge-core/src/main/java/forge/util/lang/LangChinese.java b/forge-core/src/main/java/forge/util/lang/LangChinese.java index fabe54ca173..66469b8df49 100644 --- a/forge-core/src/main/java/forge/util/lang/LangChinese.java +++ b/forge-core/src/main/java/forge/util/lang/LangChinese.java @@ -19,4 +19,9 @@ public class LangChinese extends Lang { return getPossesive(owner) + object; } + @Override + public String getNickName(final String name) { + return name; + } + } diff --git a/forge-core/src/main/java/forge/util/lang/LangEnglish.java b/forge-core/src/main/java/forge/util/lang/LangEnglish.java index d47cd6b416f..97493a30ba7 100644 --- a/forge-core/src/main/java/forge/util/lang/LangEnglish.java +++ b/forge-core/src/main/java/forge/util/lang/LangEnglish.java @@ -30,4 +30,8 @@ public class LangEnglish extends Lang { return getPossesive(owner) + " " + object; } + @Override + public String getNickName(final String name) { + return name.split(", ")[0]; + } } diff --git a/forge-core/src/main/java/forge/util/lang/LangGerman.java b/forge-core/src/main/java/forge/util/lang/LangGerman.java index 27dc6d1fae0..ebd289df7da 100644 --- a/forge-core/src/main/java/forge/util/lang/LangGerman.java +++ b/forge-core/src/main/java/forge/util/lang/LangGerman.java @@ -26,4 +26,10 @@ public class LangGerman extends Lang { return getPossesive(owner) + " " + object; } + + @Override + public String getNickName(final String name) { + return name.split(", ")[0]; + } + } diff --git a/forge-core/src/main/java/forge/util/lang/LangItalian.java b/forge-core/src/main/java/forge/util/lang/LangItalian.java index 3d5d43524a0..2ac24b5c55a 100644 --- a/forge-core/src/main/java/forge/util/lang/LangItalian.java +++ b/forge-core/src/main/java/forge/util/lang/LangItalian.java @@ -23,4 +23,9 @@ public class LangItalian extends Lang { return getPossesive(owner) + " " + object; } + @Override + public String getNickName(final String name) { + return name.split(", ")[0]; + } + } diff --git a/forge-core/src/main/java/forge/util/lang/LangJapanese.java b/forge-core/src/main/java/forge/util/lang/LangJapanese.java index 3fe6e87f67a..a4bf240c774 100644 --- a/forge-core/src/main/java/forge/util/lang/LangJapanese.java +++ b/forge-core/src/main/java/forge/util/lang/LangJapanese.java @@ -19,4 +19,11 @@ public class LangJapanese extends Lang { return getPossesive(owner) + object; } + @Override + public String getNickName(final String name) { + String [] splitName = name.split("、"); + if (splitName.length > 1) return splitName[1]; + return name; + } + } diff --git a/forge-core/src/main/java/forge/util/lang/LangSpanish.java b/forge-core/src/main/java/forge/util/lang/LangSpanish.java index f3d8d096937..47e0844b0b1 100644 --- a/forge-core/src/main/java/forge/util/lang/LangSpanish.java +++ b/forge-core/src/main/java/forge/util/lang/LangSpanish.java @@ -23,4 +23,9 @@ public class LangSpanish extends Lang { return getPossesive(owner) + " " + object; } + @Override + public String getNickName(final String name) { + return name.split(", ")[0]; + } + } diff --git a/forge-game/src/main/java/forge/game/ability/SpellAbilityEffect.java b/forge-game/src/main/java/forge/game/ability/SpellAbilityEffect.java index e03ac4e42cd..c5efe07fc58 100644 --- a/forge-game/src/main/java/forge/game/ability/SpellAbilityEffect.java +++ b/forge-game/src/main/java/forge/game/ability/SpellAbilityEffect.java @@ -87,7 +87,7 @@ public abstract class SpellAbilityEffect { // by typing "SpellDescription" they want to bypass the Effect's string builder if ("SpellDescription".equalsIgnoreCase(stackDesc)) { 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()) { sb.append(" (Targeting: ").append(sa.getTargets()).append(")"); @@ -98,7 +98,7 @@ public abstract class SpellAbilityEffect { } else { final String conditionDesc = sa.getParam("ConditionDescription"); 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) { sb.append(conditionDesc).append(" "); } @@ -131,8 +131,8 @@ public abstract class SpellAbilityEffect { } String currentName = (sa.getHostCard().getName()); - String substitutedDesc = TextUtil.fastReplace(sb.toString(), "CARDNAME", currentName); - substitutedDesc = TextUtil.fastReplace(substitutedDesc, "NICKNAME", currentName.split(",")[0]); + String substitutedDesc = TextUtil.fastReplace(sb.toString(), "CARDNAME", CardTranslation.getTranslatedName(currentName)); + substitutedDesc = TextUtil.fastReplace(substitutedDesc, "NICKNAME", Lang.getInstance().getNickName(CardTranslation.getTranslatedName(currentName))); return substitutedDesc; } diff --git a/forge-game/src/main/java/forge/game/ability/effects/PermanentNoncreatureEffect.java b/forge-game/src/main/java/forge/game/ability/effects/PermanentNoncreatureEffect.java index c5e8fa78f8b..4df7c8d975f 100644 --- a/forge-game/src/main/java/forge/game/ability/effects/PermanentNoncreatureEffect.java +++ b/forge-game/src/main/java/forge/game/ability/effects/PermanentNoncreatureEffect.java @@ -2,6 +2,7 @@ package forge.game.ability.effects; import forge.game.card.Card; import forge.game.spellability.SpellAbility; +import forge.util.CardTranslation; /** * TODO: Write javadoc for this type. @@ -13,6 +14,6 @@ public class PermanentNoncreatureEffect extends PermanentEffect { public String getStackDescription(final SpellAbility sa) { final Card sourceCard = sa.getHostCard(); //CardView toString return translated name,don't need call CardTranslation.getTranslatedName in this. - return sourceCard.getName(); + return CardTranslation.getTranslatedName(sourceCard.getName()); } } diff --git a/forge-game/src/main/java/forge/game/card/Card.java b/forge-game/src/main/java/forge/game/card/Card.java index 0486ac400c5..396397cf88b 100644 --- a/forge-game/src/main/java/forge/game/card/Card.java +++ b/forge-game/src/main/java/forge/game/card/Card.java @@ -2081,7 +2081,7 @@ public class Card extends GameEntity implements Comparable, IHasSVars { sbLong.append("\r\n"); } sb.append(sbLong); - return sb.toString(); + return CardTranslation.translateMultipleDescriptionText(sb.toString(), getName()); } private static String getTextForKwCantBeBlockedByAmount(final String keyword) { @@ -2119,7 +2119,7 @@ public class Card extends GameEntity implements Comparable, IHasSVars { while (result.endsWith("\r\n")) { 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) { @@ -2140,7 +2140,9 @@ public class Card extends GameEntity implements Comparable, IHasSVars { for (final ReplacementEffect replacementEffect : state.getReplacementEffects()) { if (!replacementEffect.isSecondary()) { 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"); } else { replacementEffects.append(text).append("\r\n"); @@ -2226,8 +2228,10 @@ public class Card extends GameEntity implements Comparable, IHasSVars { // add Adventure to AbilityText if (sa.isAdventure() && state.getView().getState().equals(CardStateName.Original)) { + CardState advState = getState(CardStateName.Adventure); 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(": "); sbSA.append(sAbility); @@ -2299,7 +2303,7 @@ public class Card extends GameEntity implements Comparable, IHasSVars { 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) { desc = TextUtil.fastReplace(desc, "EFFECTSOURCE", getEffectSource().getName()); } @@ -2485,7 +2489,7 @@ public class Card extends GameEntity implements Comparable, IHasSVars { } } - sb.append(sbBefore); + sb.append(CardTranslation.translateMultipleDescriptionText(sbBefore.toString(), state.getName())); // add Spells there to main StringBuilder sb.append(strSpell); @@ -2514,13 +2518,13 @@ public class Card extends GameEntity implements Comparable, IHasSVars { } } - sb.append(sbAfter); + sb.append(CardTranslation.translateMultipleDescriptionText(sbAfter.toString(), state.getName())); return sb; } private String formatSpellAbility(final SpellAbility sa) { final StringBuilder sb = new StringBuilder(); - sb.append(sa.toString()).append("\r\n"); + sb.append(sa.toString()).append("\r\n\r\n"); return sb.toString(); } diff --git a/forge-game/src/main/java/forge/game/card/CardFactory.java b/forge-game/src/main/java/forge/game/card/CardFactory.java index d270e7c0bcd..059b28c9081 100644 --- a/forge-game/src/main/java/forge/game/card/CardFactory.java +++ b/forge-game/src/main/java/forge/game/card/CardFactory.java @@ -38,6 +38,7 @@ import forge.game.trigger.TriggerHandler; import forge.game.trigger.WrappedAbility; import forge.item.IPaperCard; import forge.item.PaperCard; +import forge.util.CardTranslation; import forge.util.TextUtil; import java.util.Arrays; @@ -354,6 +355,10 @@ public class CardFactory { } 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 c.setName(face.getName()); diff --git a/forge-game/src/main/java/forge/game/card/CardView.java b/forge-game/src/main/java/forge/game/card/CardView.java index db12e1e34f0..c84a69a34b9 100644 --- a/forge-game/src/main/java/forge/game/card/CardView.java +++ b/forge-game/src/main/java/forge/game/card/CardView.java @@ -591,14 +591,8 @@ public class CardView extends GameEntityView { if (translationsText != null) { tname = translationsText.get("name"); taltname = translationsText.get("altname"); - - // TODO: Translate for cloned or mutated cards - // 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"); - } + toracle = translationsText.get("oracle"); + taltoracle = translationsText.get("altoracle"); } if (isSplitCard()) { @@ -629,18 +623,7 @@ public class CardView extends GameEntityView { } final String rulesText = state.getRulesText(); - if (!toracle.isEmpty() && !isFaceDown()) { - 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()) { + if (!rulesText.isEmpty()) { sb.append(rulesText).append("\r\n\r\n"); } if (isCommander()) { @@ -648,15 +631,13 @@ public class CardView extends GameEntityView { sb.append(getOwner().getCommanderInfo(this)).append("\r\n"); } - if (toracle.isEmpty()) { - if (isSplitCard() && !isFaceDown() && getZone() != ZoneType.Stack) { - sb.append("(").append(getLeftSplitState().getName()).append(") "); - sb.append(getLeftSplitState().getAbilityText()); - sb.append("\r\n\r\n").append("(").append(getRightSplitState().getName()).append(") "); - sb.append(getRightSplitState().getAbilityText()); - } else { - sb.append(state.getAbilityText()); - } + if (isSplitCard() && !isFaceDown() && getZone() != ZoneType.Stack) { + sb.append("(").append(getLeftSplitState().getName()).append(") "); + sb.append(getLeftSplitState().getAbilityText()); + sb.append("\r\n\r\n").append("(").append(getRightSplitState().getName()).append(") "); + sb.append(getRightSplitState().getAbilityText()); + } else { + sb.append(state.getAbilityText()); } String nonAbilityText = get(TrackableProperty.NonAbilityText); @@ -1093,7 +1074,7 @@ public class CardView extends GameEntityView { return get(TrackableProperty.OracleText); } 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() { diff --git a/forge-game/src/main/java/forge/game/cost/CostExile.java b/forge-game/src/main/java/forge/game/cost/CostExile.java index 74090fe038b..58e69a36baa 100644 --- a/forge-game/src/main/java/forge/game/cost/CostExile.java +++ b/forge-game/src/main/java/forge/game/cost/CostExile.java @@ -27,7 +27,6 @@ import forge.game.spellability.SpellAbility; import forge.game.zone.ZoneType; import forge.util.TextUtil; import forge.util.collect.FCollectionView; -import forge.util.Localizer; /** * The Class CostExile. @@ -89,32 +88,32 @@ public class CostExile extends CostPartWithList { if (this.payCostFromSource()) { 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")) { - 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.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 (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) { - 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 diff --git a/forge-game/src/main/java/forge/game/replacement/ReplacementEffect.java b/forge-game/src/main/java/forge/game/replacement/ReplacementEffect.java index d1aa184e48f..1b1a6bd2fb6 100644 --- a/forge-game/src/main/java/forge/game/replacement/ReplacementEffect.java +++ b/forge-game/src/main/java/forge/game/replacement/ReplacementEffect.java @@ -25,6 +25,8 @@ import forge.game.ability.AbilityUtils; import forge.game.card.Card; import forge.game.phase.PhaseType; import forge.game.spellability.SpellAbility; +import forge.util.CardTranslation; +import forge.util.Lang; import forge.util.TextUtil; import java.util.List; @@ -214,15 +216,13 @@ public abstract class ReplacementEffect extends TriggerReplacementBase { public String getDescription() { if (hasParam("Description") && !this.isSuppressed()) { String desc = AbilityUtils.applyDescriptionTextChangeEffects(getParam("Description"), this); - if (desc.contains("CARDNAME")) { - desc = TextUtil.fastReplace(desc, "CARDNAME", getHostCard().toString()); - } + String currentName = getHostCard().getName(); + 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")) { desc = TextUtil.fastReplace(desc, "EFFECTSOURCE", getHostCard().getEffectSource().toString()); } - if (desc.contains("NICKNAME")) { - desc = TextUtil.fastReplace(desc, "NICKNAME", getHostCard().toString().split(",")[0]); - } return desc; } else { return ""; diff --git a/forge-game/src/main/java/forge/game/spellability/SpellAbility.java b/forge-game/src/main/java/forge/game/spellability/SpellAbility.java index 48445ae770d..78647c900b0 100644 --- a/forge-game/src/main/java/forge/game/spellability/SpellAbility.java +++ b/forge-game/src/main/java/forge/game/spellability/SpellAbility.java @@ -54,7 +54,9 @@ import forge.game.trigger.TriggerType; import forge.game.trigger.WrappedAbility; import forge.game.zone.ZoneType; import forge.util.Aggregates; +import forge.util.CardTranslation; import forge.util.Expressions; +import forge.util.Lang; import forge.util.TextUtil; import org.apache.commons.lang3.StringUtils; @@ -822,8 +824,10 @@ public abstract class SpellAbility extends CardTraitBase implements ISpellAbilit } String desc = node.getDescription(); if (node.getHostCard() != null) { - desc = TextUtil.fastReplace(desc, "CARDNAME", node.getHostCard().getName()); - desc = TextUtil.fastReplace(desc,"NICKNAME",node.getHostCard().getName().split(",")[0]); + String currentName = node.getHostCard().getName(); + 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) { desc = TextUtil.fastReplace(desc, "ORIGINALHOST", node.getOriginalHost().getName()); } diff --git a/forge-game/src/main/java/forge/game/staticability/StaticAbility.java b/forge-game/src/main/java/forge/game/staticability/StaticAbility.java index f5fafe057fd..bd709f86c3a 100644 --- a/forge-game/src/main/java/forge/game/staticability/StaticAbility.java +++ b/forge-game/src/main/java/forge/game/staticability/StaticAbility.java @@ -39,7 +39,9 @@ import forge.game.player.Player; import forge.game.spellability.SpellAbility; import forge.game.zone.Zone; import forge.game.zone.ZoneType; +import forge.util.CardTranslation; import forge.util.Expressions; +import forge.util.Lang; import forge.util.TextUtil; import java.util.EnumSet; @@ -214,9 +216,10 @@ public class StaticAbility extends CardTraitBase implements IIdentifiable, Clone @Override public final String toString() { if (hasParam("Description") && !this.isSuppressed()) { - String desc = getParam("Description"); - desc = TextUtil.fastReplace(desc, "CARDNAME", this.hostCard.getName()); - desc = TextUtil.fastReplace(desc, "NICKNAME", this.hostCard.getName().split(",")[0]); + String currentName = this.hostCard.getName(); + String desc = CardTranslation.translateSingleDescriptionText(getParam("Description"), currentName); + desc = TextUtil.fastReplace(desc, "CARDNAME", CardTranslation.getTranslatedName(currentName)); + desc = TextUtil.fastReplace(desc, "NICKNAME", Lang.getInstance().getNickName(CardTranslation.getTranslatedName(currentName))); return desc; } else { diff --git a/forge-game/src/main/java/forge/game/trigger/Trigger.java b/forge-game/src/main/java/forge/game/trigger/Trigger.java index 811f2aa7371..82c7b9994ca 100644 --- a/forge-game/src/main/java/forge/game/trigger/Trigger.java +++ b/forge-game/src/main/java/forge/game/trigger/Trigger.java @@ -39,6 +39,8 @@ import java.util.*; import com.google.common.collect.Lists; import com.google.common.collect.Sets; +import forge.util.CardTranslation; +import forge.util.Lang; import forge.util.TextUtil; /** @@ -124,9 +126,13 @@ public abstract class Trigger extends TriggerReplacementBase { if (hasParam("TriggerDescription") && !this.isSuppressed()) { StringBuilder sb = new StringBuilder(); - String currentName = (getHostCard().getName()); - String desc1 = TextUtil.fastReplace(getParam("TriggerDescription"),"CARDNAME", currentName); - String desc = TextUtil.fastReplace(desc1,"NICKNAME", currentName.split(",")[0]); + String currentName = getHostCard().getName(); + String desc = getParam("TriggerDescription"); + 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(active) desc = TextUtil.fastReplace(desc, "EFFECTSOURCE", getHostCard().getEffectSource().toString()); @@ -198,6 +204,11 @@ public abstract class Trigger extends TriggerReplacementBase { saDesc = ""; // printed in case nothing is chosen for the ability (e.g. Charm with Up to X) } 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; diff --git a/forge-gui-desktop/src/test/java/forge/ai/simulation/SimulationTestCase.java b/forge-gui-desktop/src/test/java/forge/ai/simulation/SimulationTestCase.java index 4cac504d5f9..a253dc8391c 100644 --- a/forge-gui-desktop/src/test/java/forge/ai/simulation/SimulationTestCase.java +++ b/forge-gui-desktop/src/test/java/forge/ai/simulation/SimulationTestCase.java @@ -43,6 +43,7 @@ public class SimulationTestCase extends TestCase { @Override public Void apply(ForgePreferences preferences) { preferences.setPref(FPref.LOAD_CARD_SCRIPTS_LAZILY, false); + preferences.setPref(FPref.UI_LANGUAGE, "en-US"); return null; } }); diff --git a/forge-gui/res/languages/de-DE.properties b/forge-gui/res/languages/de-DE.properties index 868c6786fe0..8fb0cdf5f92 100644 --- a/forge-gui/res/languages/de-DE.properties +++ b/forge-gui/res/languages/de-DE.properties @@ -1417,6 +1417,8 @@ lblOtherFormats=Andere Formate... lblChooseSets=Wähle Sets... #HistoricFormatSelect.java lblChooseFormat=Wähle Format +#Card.java +lblAdventure=Adventure #TriggerAdapt.java lblAdapt=Adaptieren #TriggerAttached.java @@ -2286,14 +2288,6 @@ lblCostPaymentInvalid=Bezahlung der Kosten unmöglich lblSelectATargetToTap=Wähle ein(e) {0} zum Tappen (noch {1}) lblSelectATargetToUntap=Wähle ein(e) {0} zum Enttappen (noch {1}) 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 lblChooseFirstApplyReplacementEffect=Welchen Ersatzeffekt zuerst anwenden? lblApplyCardReplacementEffectToCardConfirm=Wende Ersatzeffekt von {0} auf {1} an?\r\n{2} diff --git a/forge-gui/res/languages/en-US.properties b/forge-gui/res/languages/en-US.properties index bd5d5fd7130..6314c54ed89 100644 --- a/forge-gui/res/languages/en-US.properties +++ b/forge-gui/res/languages/en-US.properties @@ -1417,6 +1417,8 @@ lblOtherFormats=Other Formats lblChooseSets=Choose Sets #HistoricFormatSelect.java lblChooseFormat=Choose Format +#Card.java +lblAdventure=Adventure #TriggerAdapt.java lblAdapt=Adapt #TriggerAttached.java @@ -2285,14 +2287,6 @@ lblCostPaymentInvalid=Cost payment invalid lblSelectATargetToTap=Select a {0} to tap ({1} left) lblSelectATargetToUntap=Select a {0} to untap ({1} left) 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 lblChooseFirstApplyReplacementEffect=Choose a replacement effect to apply first. lblApplyCardReplacementEffectToCardConfirm=Apply replacement effect of {0} to {1}?\r\n{2} diff --git a/forge-gui/res/languages/es-ES.properties b/forge-gui/res/languages/es-ES.properties index 1a9f7d443c9..7f33dd2dbb9 100644 --- a/forge-gui/res/languages/es-ES.properties +++ b/forge-gui/res/languages/es-ES.properties @@ -1417,6 +1417,8 @@ lblOtherFormats=Otros formatos... lblChooseSets=Selecciona ediciones... #HistoricFormatSelect.java lblChooseFormat=Selecciona formato +#Card.java +lblAdventure=Adventure #TriggerAdapt.java lblAdapt=Adaptar #TriggerAttached.java @@ -2284,14 +2286,6 @@ lblCostPaymentInvalid=Pago del coste no válido lblSelectATargetToTap=Selecciona un/a {0} para girar ({1} pendiente) lblSelectATargetToUntap=Selecciona un/a {0} para enderezar ({1} pendiente) 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 lblChooseFirstApplyReplacementEffect=Elige un efecto de reemplazo para aplicar primero. lblApplyCardReplacementEffectToCardConfirm=¿Aplicar efecto de reemplazo de {0} a {1}?\r\n{2} diff --git a/forge-gui/res/languages/it-IT.properties b/forge-gui/res/languages/it-IT.properties index b2069c38535..d466f9806c9 100644 --- a/forge-gui/res/languages/it-IT.properties +++ b/forge-gui/res/languages/it-IT.properties @@ -1417,6 +1417,8 @@ lblOtherFormats=Other Formats... lblChooseSets=Choose Sets... #HistoricFormatSelect.java lblChooseFormat=Choose Format +#Card.java +lblAdventure=Adventure #TriggerAdapt.java lblAdapt=Adapt #TriggerAttached.java @@ -2286,14 +2288,6 @@ lblCostPaymentInvalid=Cost payment invalid lblSelectATargetToTap=Select a {0} to tap ({1} left) lblSelectATargetToUntap=Select a {0} to untap ({1} left) 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 lblChooseFirstApplyReplacementEffect=Choose a replacement effect to apply first. lblApplyCardReplacementEffectToCardConfirm=Apply replacement effect of {0} to {1}?\r\n{2} diff --git a/forge-gui/res/languages/ja-JP.properties b/forge-gui/res/languages/ja-JP.properties index fa718c3a3d3..c22e1a9887c 100644 --- a/forge-gui/res/languages/ja-JP.properties +++ b/forge-gui/res/languages/ja-JP.properties @@ -1417,6 +1417,8 @@ lblOtherFormats=他のフォーマット... lblChooseSets=セットを選択 #HistoricFormatSelect.java lblChooseFormat=フォーマットを選択 +#Card.java +lblAdventure=出来事 #TriggerAdapt.java lblAdapt=順応 #TriggerAttached.java @@ -2285,14 +2287,6 @@ lblCostPaymentInvalid=無効な支払い lblSelectATargetToTap=タップする {0}を選択(残り{1}) lblSelectATargetToUntap=アンタップする {0}を選択(残り{1}) lblUnattachCardConfirm={0}を外しますか? -#CostExile.java -lblExileTargetsFromYourZone=あなたの {1}から {0}を追放 -lblExileTarget={0}を追放 -lblExileAllCardsFromYourZone=あなたの {0}からすべてのカードを追放 -lblExileTargetsYourControl=あなたがコントロールする {0}を追放 -lblExileNCardFromSameZone=同じ {1}から {0}カードを追放 -lblExileNCardFromYourZone=あなたの {1}から {0}カードを追放 -lblExileNTargetFromSameZone=同じ {1}から {0}を追放 #ReplacementHandler.java lblChooseFirstApplyReplacementEffect=最初に適用する置換効果を選択してください。 lblApplyCardReplacementEffectToCardConfirm={0}の置換効果を{1}に適用しますか?\r\n{2} diff --git a/forge-gui/res/languages/zh-CN.properties b/forge-gui/res/languages/zh-CN.properties index 9588d12e1ab..a13226cd856 100644 --- a/forge-gui/res/languages/zh-CN.properties +++ b/forge-gui/res/languages/zh-CN.properties @@ -1415,6 +1415,8 @@ lblOtherFormats=其他赛制 lblChooseSets=选择系列 #HistoricFormatSelect.java lblChooseFormat=选择赛制 +#Card.java +lblAdventure=Adventure #TriggerAdapt.java lblAdapt=演化 #TriggerAttached.java @@ -2283,14 +2285,6 @@ lblCostPaymentInvalid=付费失败 lblSelectATargetToTap=选择{0}进行横置(还剩{1}) lblSelectATargetToUntap=选择{0}进行重置(还剩{1}) lblUnattachCardConfirm=取消结附{0}? -#CostExile.java -lblExileTargetsFromYourZone=从你的{1}放逐{0} -lblExileTarget=放逐{0} -lblExileAllCardsFromYourZone=从你的{0}中放逐所有牌 -lblExileTargetsYourControl=放逐你操控的{0} -lblExileNCardFromSameZone=从同一{1}中的卡牌中放逐{0}张牌 -lblExileNCardFromYourZone=从你的{1}中放逐{0}张牌 -lblExileNTargetFromSameZone=从同一{1}中的卡牌中放逐{0} #ReplacementHandler.java lblChooseFirstApplyReplacementEffect=选择首先生效的替代性效应。 lblApplyCardReplacementEffectToCardConfirm=对{1}应用替代性效应{0}?\r\n{2}