Merge branch 'ui-card-translation' into 'master'

UI Card Translation (desktop and mobile)

See merge request core-developers/forge!1987
This commit is contained in:
swordshine
2019-09-11 13:27:55 +00:00
15 changed files with 36857 additions and 23 deletions

View File

@@ -112,7 +112,7 @@ public class Localizer {
public static class Language {
public String languageName;
public String langaugeID;
public String languageID;
}
}

View File

@@ -536,27 +536,50 @@ public class CardView extends GameEntityView {
}
public String getText() {
return getText(getCurrentState());
return getText(getCurrentState(), null);
}
public String getText(CardStateView state) {
public String getText(CardStateView state, HashMap<String, String> translationsText) {
final StringBuilder sb = new StringBuilder();
//final boolean isSplitCard = (state.getState() == CardStateName.LeftSplit);
String tname = "", toracle = "", taltname = "", taltoracle = "";
// If we have translations, use them
if (translationsText != null) {
tname = translationsText.get("name");
taltname = translationsText.get("altname");
// Don't translate oracles if the card is a cloned one
if (((String) get(TrackableProperty.Cloner)).isEmpty()) {
toracle = translationsText.get("oracle");
taltoracle = translationsText.get("altoracle");
}
}
tname = tname.isEmpty() ? state.getName() : tname;
toracle = toracle.isEmpty() ? state.getOracleText() : toracle;
if (isSplitCard()) {
taltname = getAlternateState().getName();
taltoracle = getAlternateState().getOracleText();
}
if (getId() < 0) {
if (isSplitCard()) {
sb.append("(").append(state.getName()).append(") ");
sb.append(state.getOracleText());
sb.append("(").append(tname).append(") ");
sb.append(toracle);
sb.append("\r\n\r\n");
sb.append("(").append(getAlternateState().getName()).append(") ");
sb.append(getAlternateState().getOracleText());
sb.append("(").append(taltname).append(") ");
sb.append(taltoracle);
return sb.toString().trim();
} else {
return state.getOracleText();
return toracle;
}
}
final String rulesText = state.getRulesText();
if (!rulesText.isEmpty()) {
if (!toracle.isEmpty()) {
sb.append(toracle).append("\r\n\r\n");
} else if (!rulesText.isEmpty()) {
sb.append(rulesText).append("\r\n\r\n");
}
if (isCommander()) {
@@ -565,6 +588,7 @@ public class CardView extends GameEntityView {
}
if (isSplitCard() && !isFaceDown()) {
// TODO: Translation?
CardStateView view = state.getState() == CardStateName.LeftSplit ? state : getAlternateState();
if (getZone() != ZoneType.Stack) {
sb.append("(");
@@ -573,12 +597,12 @@ public class CardView extends GameEntityView {
}
sb.append(view.getAbilityText());
} else {
sb.append(state.getAbilityText());
if (toracle.isEmpty()) sb.append(state.getAbilityText());
}
if (isSplitCard() && !isFaceDown() && getZone() != ZoneType.Stack) {
//ensure ability text for right half of split card is included unless spell is on stack
sb.append("\r\n\r\n").append("(").append(getAlternateState().getName()).append(") ").append(getAlternateState().getOracleText());
sb.append("\r\n\r\n").append("(").append(taltname).append(") ").append(taltoracle);
}
String nonAbilityText = get(TrackableProperty.NonAbilityText);

View File

@@ -21,6 +21,7 @@ import forge.CachedCardImage;
import forge.FThreads;
import forge.StaticData;
import forge.card.CardEdition;
import forge.card.CardTranslation;
import forge.card.mana.ManaCost;
import forge.game.card.Card;
import forge.game.card.CardView;
@@ -690,7 +691,7 @@ public class CardPanel extends SkinnedPanel implements CardContainer, IDisposabl
}
// Card name overlay
titleText.setText(card.getCurrentState().getName());
titleText.setText(CardTranslation.getTranslatedName(card.getCurrentState().getName()));
final int damage = card.getDamage();
damageText.setText(damage > 0 ? "\u00BB " + damage + " \u00AB" : "");

View File

@@ -10,6 +10,7 @@ import forge.assets.AssetsDownloader;
import forge.assets.FSkin;
import forge.assets.FSkinFont;
import forge.assets.ImageCache;
import forge.card.CardTranslation;
import forge.error.BugReporter;
import forge.error.ExceptionHandler;
import forge.interfaces.IDeviceAdapter;
@@ -108,6 +109,9 @@ public class Forge implements ApplicationListener {
splashScreen.getProgressBar().setDescription("Loading fonts...");
FSkinFont.preloadAll();
splashScreen.getProgressBar().setDescription("Loading card translations...");
CardTranslation.preloadTranslation(prefs.getPref(FPref.UI_LANGUAGE));
splashScreen.getProgressBar().setDescription("Finishing startup...");
Gdx.app.postRunnable(new Runnable() {

View File

@@ -183,7 +183,7 @@ public class CardImageRenderer {
//draw name for card
x += padding;
w -= 2 * padding;
g.drawText(state.getName(), NAME_FONT, Color.BLACK, x, y, w - manaCostWidth - padding, h, false, Align.left, true);
g.drawText(CardTranslation.getTranslatedName(state.getName()), NAME_FONT, Color.BLACK, x, y, w - manaCostWidth - padding, h, false, Align.left, true);
}
public static final FBufferedImage forgeArt;
@@ -260,7 +260,7 @@ public class CardImageRenderer {
g.drawImage(image, x + (w - iconSize) / 2, y + (h - iconSize) / 2, iconSize, iconSize);
}
else {
final String text = card.getText(state);
final String text = card.getText(state, CardTranslation.getTranslationTexts(state.getName(), ""));
if (StringUtils.isEmpty(text)) { return; }
float padding = TEXT_FONT.getCapHeight() * 0.75f;

View File

@@ -255,7 +255,7 @@ public class CardRenderer {
state.getLoyalty(), count, suffix, x, y, w, h, compactMode);
}
else { //if fake card, just draw card name centered
String name = state.getName();
String name = CardTranslation.getTranslatedName(state.getName());
if (count > 0) { //preface name with count if applicable
name = count + " " + name;
}
@@ -317,7 +317,7 @@ public class CardRenderer {
CardFaceSymbols.drawManaCost(g, mainManaCost, x + w - manaCostWidth, y, MANA_SYMBOL_SIZE);
x += cardArtWidth;
String name = card.getCurrentState().getName();
String name = CardTranslation.getTranslatedName(card.getCurrentState().getName());
if (count > 0) { //preface name with count if applicable
name = count + " " + name;
}
@@ -448,7 +448,7 @@ public class CardRenderer {
//draw name and mana cost overlays if card is small or default card image being used
if (h <= NAME_COST_THRESHOLD && canShow) {
if (showCardNameOverlay(card)) {
g.drawOutlinedText(details.getName(), FSkinFont.forHeight(h * 0.18f), Color.WHITE, Color.BLACK, x + padding, y + padding, w - 2 * padding, h * 0.4f, true, Align.left, false);
g.drawOutlinedText(CardTranslation.getTranslatedName(details.getName()), FSkinFont.forHeight(h * 0.18f), Color.WHITE, Color.BLACK, x + padding, y + padding, w - 2 * padding, h * 0.4f, true, Align.left, false);
}
if (showCardManaCostOverlay(card)) {
float manaSymbolSize = w / 4;

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -160,11 +160,13 @@ public class CardDetailUtil {
public static String formatCardName(final CardView card, final boolean canShow, final boolean forAltState) {
final String name = forAltState ? card.getAlternateState().getName() : card.getName();
return StringUtils.isEmpty(name) || !canShow ? "???" : name.trim();
String translatedname = CardTranslation.getTranslatedName(name);
return StringUtils.isEmpty(translatedname) || !canShow ? "???" : translatedname.trim();
}
public static String formatCardType(final CardStateView card, final boolean canShow) {
return canShow ? card.getType().toString() : (card.getState() == CardStateName.FaceDown ? "Creature" : "---");
String translatedtype = CardTranslation.getTranslatedType(card.getName(), card.getType().toString());
return canShow ? translatedtype : (card.getState() == CardStateName.FaceDown ? "Creature" : "---");
}
public static String formatPowerToughness(final CardStateView card, final boolean canShow) {
@@ -276,7 +278,9 @@ public class CardDetailUtil {
if (area.length() != 0) {
area.append("\n");
}
String text = card.getText(state);
String text = card.getText(state, CardTranslation.getTranslationTexts(state.getName(), ""));
// LEVEL [0-9]+-[0-9]+
// LEVEL [0-9]+\+

View File

@@ -0,0 +1,91 @@
package forge.card;
import com.esotericsoftware.minlog.Log;
import com.google.common.base.Charsets;
import forge.properties.ForgeConstants;
import forge.util.LineReader;
import java.io.FileInputStream;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
public class CardTranslation {
private static Map <String, String> translatednames;
private static Map <String, String> translatedtypes;
private static Map <String, String> translatedoracles;
private static String languageSelected;
private static void readTranslationFile(String language) {
String filename = "cardnames-" + language + ".txt";
try (LineReader translationFile = new LineReader(new FileInputStream(ForgeConstants.LANG_DIR + filename), Charsets.UTF_8)) {
for (String line : translationFile.readLines()) {
String[] matches = line.split("\\|");
if (matches.length >= 2) {
translatednames.put(matches[0], matches[1]);
}
if (matches.length >= 3) {
translatedtypes.put(matches[0], matches[2]);
}
if (matches.length >= 4) {
translatedoracles.put(matches[0], matches[3].replace("\\n", "\n\n"));
}
}
} catch (IOException e) {
Log.error("Error reading translation file: cardnames-" + language + ".txt");
}
}
public static String getTranslatedName(String name) {
if (needsTranslation()) {
String tname = translatednames.get(name);
return tname == null ? name : tname;
}
return name;
}
public static String getTranslatedType(String name, String originaltype) {
if (needsTranslation()) {
String ttype = translatedtypes.get(name);
return ttype == null ? originaltype : ttype;
}
return originaltype;
}
public static String getTranslatedOracle(String name) {
if (needsTranslation()) {
String toracle = translatedoracles.get(name);
return toracle == null ? "" : toracle;
}
return "";
}
public static HashMap<String, String> getTranslationTexts(String cardname, String altcardname) {
HashMap<String, String> translations = new HashMap<String, String>();
translations.put("name", getTranslatedName(cardname));
translations.put("oracle", getTranslatedOracle(cardname));
translations.put("altname", getTranslatedName(altcardname));
translations.put("altoracle", getTranslatedOracle(altcardname));
return translations;
}
private static boolean needsTranslation() {
return !languageSelected.equals("en-US");
}
public static void preloadTranslation(String language) {
languageSelected = language;
if (needsTranslation()) {
translatednames = new HashMap<>();
translatedtypes = new HashMap<>();
translatedoracles = new HashMap<>();
readTranslationFile(languageSelected);
}
}
}

View File

@@ -24,6 +24,7 @@ import forge.CardStorageReader.ProgressObserver;
import forge.achievement.*;
import forge.ai.AiProfileUtil;
import forge.card.CardPreferences;
import forge.card.CardTranslation;
import forge.card.CardType;
import forge.deck.CardArchetypeLDAGenerator;
import forge.deck.CardRelationMatrixGenerator;
@@ -146,6 +147,7 @@ public final class FModel {
final CardStorageReader tokenReader = new CardStorageReader(ForgeConstants.TOKEN_DATA_DIR, progressBarBridge,
FModel.getPreferences().getPrefBoolean(FPref.LOAD_CARD_SCRIPTS_LAZILY));
magicDb = new StaticData(reader, tokenReader, ForgeConstants.EDITIONS_DIR, ForgeConstants.BLOCK_DATA_DIR);
CardTranslation.preloadTranslation(preferences.getPref(FPref.UI_LANGUAGE));
//create profile dirs if they don't already exist
for (final String dname : ForgeConstants.PROFILE_DIRS) {

View File

@@ -0,0 +1,6 @@
Forest|Bosque|Tierra básica - Bosque|({T}: Agrega {G}.)
Island|Isla|Tierra básica - Isla|({T}: Agrega {U}.)
Mountain|Montaña|Tierra básica - Montaña|({T}: Agrega {R}.)
Plains|Llanura|Tierra básica - Llanura|({T}: Agrega {W}.)
Swamp|Pantano|Tierra básica - Pantano|({T}: Agrega {B}.)

View File

@@ -0,0 +1,164 @@
import json
import os
import re
import urllib.request
database = 'scryfall-all-cards.json'
scryfalldburl = 'https://archive.scryfall.com/json/' + database
languages = ['es', 'de']
urllib.request.urlretrieve(scryfalldburl, database)
# Sort file and remove duplicates
def cleanfile(filename):
names_seen = set()
outfile = open(filename + ".tmp2", "w", encoding='utf8')
with open(filename + ".tmp", "r", encoding='utf8') as r:
for line in sorted(r):
name = line.split('|')[0]
if name not in names_seen:
outfile.write(line)
names_seen.add(name)
outfile.close()
os.remove(filename + ".tmp")
# Manual patch of file translations
def patchtranslations(filename):
ffinal = open(filename + '.txt', 'w', encoding='utf8')
fpatch = open(filename + '-patch.txt', 'r', encoding='utf8')
patchline = fpatch.readline()
with open(filename + '.tmp2', 'r', encoding='utf8') as temp:
for templine in temp:
tempname = templine.split('|')[0]
patchname = patchline.split('|')[0]
if patchname == tempname:
ffinal.write(patchline)
patchline = fpatch.readline()
else:
ffinal.write(templine)
ffinal.close()
fpatch.close()
os.remove(filename + '.tmp2')
with open(database, mode='r', encoding='utf8') as json_file:
data = json.load(json_file)
feses = open('cardnames-es-ES.tmp', 'w', encoding='utf8')
fdede = open('cardnames-de-DE.tmp', 'w', encoding='utf8')
for card in data:
if card['lang'] in languages:
try:
name = card['name']
except:
pass
# Parse simple card
if ' // ' not in name:
tname = ttype = toracle = ''
try:
tname = card['printed_name']
except:
pass
try:
ttype = card['printed_type_line']
except:
pass
try:
toracle = card['printed_text']
except:
pass
output = name + '|' + tname + '|' + ttype
output = output + '|' + toracle
output = output.replace('\n', '\\n')
output = output + '\n'
if card['lang'] == 'es':
feses.write(output)
if card['lang'] == 'de':
fdede.write(output)
# Parse double card
else:
tname0 = tname1 = ttype0 = ttype1 = toracle0 = toracle1 = ''
cardfaces = card['card_faces']
try:
name0 = cardfaces[0]['name']
except:
pass
try:
name1 = cardfaces[1]['name']
except:
pass
try:
tname0 = cardfaces[0]['printed_name']
except:
pass
try:
tname1 = cardfaces[1]['printed_name']
except:
pass
try:
ttype0 = cardfaces[0]['printed_type_line']
except:
pass
try:
ttype1 = cardfaces[1]['printed_type_line']
except:
pass
try:
toracle0 = cardfaces[0]['printed_text']
except:
pass
try:
toracle1 = cardfaces[1]['printed_text']
except:
pass
# Output Card0
output0 = name0 + '|' + tname0 + '|' + ttype0
output0 = output0 + '|' + toracle0
output0 = output0.replace('\n', '\\n')
if card['lang'] == 'es':
feses.write(output0 + '\n')
if card['lang'] == 'de':
fdede.write(output0 + '\n')
# Output Card1
output1 = name1 + '|' + tname1 + '|' + ttype1
output1 = output1 + '|' + toracle1
output1 = output1.replace('\n', '\\n')
if card['lang'] == 'es':
feses.write(output1 + '\n')
if card['lang'] == 'de':
fdede.write(output1 + '\n')
feses.close()
fdede.close()
# Sort file and remove duplicates
cleanfile("cardnames-es-ES")
cleanfile("cardnames-de-DE")
# Patch language files
patchtranslations("cardnames-es-ES")
patchtranslations("cardnames-de-DE")