Added link to Weblate from translatable strings once the translator mode

is activated in the workspace settings. This required the addition of a
new lib (in source form) for a SipHash implementation.
This commit is contained in:
Zukero
2017-04-10 18:16:17 +02:00
parent bd8576df0c
commit 0a7cb40dbc
31 changed files with 1485 additions and 8 deletions

View File

@@ -19,6 +19,8 @@ import com.gpl.rpg.atcontentstudio.model.Workspace;
import com.gpl.rpg.atcontentstudio.ui.StudioFrame;
import com.gpl.rpg.atcontentstudio.ui.WorkerDialog;
import com.gpl.rpg.atcontentstudio.ui.WorkspaceSelector;
import com.gpl.rpg.atcontentstudio.utils.HashUtils;
import com.zackehh.siphash.SipHash;
public class ATContentStudio {

View File

@@ -11,6 +11,8 @@ import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import javax.swing.ComboBoxModel;
import org.json.simple.JSONObject;
import org.json.simple.parser.JSONParser;
import org.json.simple.parser.ParseException;
@@ -39,8 +41,11 @@ public class WorkspaceSettings {
public static Boolean DEFAULT_USE_SYS_IMG_EDITOR = false;
public Setting<Boolean> useSystemDefaultImageEditor = new PrimitiveSetting<Boolean>("useSystemDefaultImageEditor", DEFAULT_USE_SYS_IMG_EDITOR);
public static String DEFAULT_IMG_EDITOR_COMMAND = "gimp";
public static String[] LANGUAGE_LIST = new String[]{null, "de", "ru", "pl", "fr", "it", "es", "nl", "uk", "ca", "sv", "pt", "pt_BR", "zh_Hant", "zh_Hans", "ja", "cs", "tr", "ko", "hu", "sl", "bg", "id", "fi", "th", "gl", "ms" ,"pa", "az"};
public Setting<String> imageEditorCommand = new PrimitiveSetting<String>("imageEditorCommand", DEFAULT_IMG_EDITOR_COMMAND);
public Setting<String> translatorLanguage = new NullDefaultPrimitiveSetting<String>("translatorLanguage");
public List<Setting<? extends Object>> settings = new ArrayList<Setting<? extends Object>>();
public WorkspaceSettings(Workspace parent) {
@@ -50,6 +55,7 @@ public class WorkspaceSettings {
settings.add(useSystemDefaultImageViewer);
settings.add(useSystemDefaultImageEditor);
settings.add(imageEditorCommand);
settings.add(translatorLanguage);
file = new File(parent.baseFolder, FILENAME);
if (file.exists()) {
load(file);
@@ -174,6 +180,18 @@ public class WorkspaceSettings {
}
public class NullDefaultPrimitiveSetting<X extends Object> extends PrimitiveSetting<X> {
public NullDefaultPrimitiveSetting(String id) {
super(id, null);
}
@Override
public void saveToJson(Map json) {
if (value != null) json.put(id, value);
}
}
public class ListSetting<X extends Object> extends Setting<List<X>> {
public ListSetting(String id, List<X> defaultValue) {

View File

@@ -72,6 +72,9 @@ public class AboutEditor extends Editor {
"<a href=\"http://www.beanshell.org/\">BeanShell</a> by Pat Niemeyer.<br/>" +
"License: <a href=\"http://www.beanshell.org/license.html\">LGPL v3</a><br/>" +
"<br/>" +
"A slightly modified version of <a href=\"https://github.com/zackehh/siphash-java\">SipHash for Java</a> by Isaac Whitfield.<br/>" +
"License: <a href=\"https://github.com/zackehh/siphash-java/blob/master/LICENSE\">MIT License</a><br/>" +
"<br/>" +
"See the tabs below to find the full license text for each of these.<br/>" +
"<br/>" +
"The Windows installer was created with:<br/>" +
@@ -121,6 +124,7 @@ public class AboutEditor extends Editor {
editorTabsHolder.add("libtiled-java License", getInfoPane(new Scanner(ATContentStudio.class.getResourceAsStream("/LICENSE.libtiled.txt"), "UTF-8").useDelimiter("\\A").next(), "text/text"));
editorTabsHolder.add("prefuse License", getInfoPane(new Scanner(ATContentStudio.class.getResourceAsStream("/license-prefuse.txt"), "UTF-8").useDelimiter("\\A").next(), "text/text"));
editorTabsHolder.add("BeanShell License", getInfoPane(new Scanner(ATContentStudio.class.getResourceAsStream("/LICENSE.LGPLv3.txt"), "UTF-8").useDelimiter("\\A").next(), "text/text"));
editorTabsHolder.add("SipHash for Java License", getInfoPane(new Scanner(ATContentStudio.class.getResourceAsStream("/LICENSE.siphash-zackehh.txt"), "UTF-8").useDelimiter("\\A").next(), "text/text"));
editorTabsHolder.add("ATCS License", getInfoPane(new Scanner(ATContentStudio.class.getResourceAsStream("/LICENSE.GPLv3.txt"), "UTF-8").useDelimiter("\\A").next(), "text/text"));
}

View File

@@ -2,6 +2,8 @@ package com.gpl.rpg.atcontentstudio.ui;
import java.awt.BorderLayout;
import java.awt.Component;
import java.awt.Cursor;
import java.awt.Desktop;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.ItemEvent;
@@ -10,6 +12,9 @@ import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
@@ -48,6 +53,7 @@ import com.gpl.rpg.atcontentstudio.Notification;
import com.gpl.rpg.atcontentstudio.model.GameDataElement;
import com.gpl.rpg.atcontentstudio.model.Project;
import com.gpl.rpg.atcontentstudio.model.ProjectElementListener;
import com.gpl.rpg.atcontentstudio.model.Workspace;
import com.gpl.rpg.atcontentstudio.model.gamedata.ActorCondition;
import com.gpl.rpg.atcontentstudio.model.gamedata.Dialogue;
import com.gpl.rpg.atcontentstudio.model.gamedata.Droplist;
@@ -57,6 +63,7 @@ import com.gpl.rpg.atcontentstudio.model.gamedata.JSONElement;
import com.gpl.rpg.atcontentstudio.model.gamedata.NPC;
import com.gpl.rpg.atcontentstudio.model.gamedata.Quest;
import com.gpl.rpg.atcontentstudio.model.maps.TMXMap;
import com.gpl.rpg.atcontentstudio.utils.HashUtils;
import com.jidesoft.swing.ComboBoxSearchable;
import com.jidesoft.swing.JideBoxLayout;
@@ -108,6 +115,58 @@ public abstract class Editor extends JPanel implements ProjectElementListener {
return addTextField(pane, label, value, false, nullListener);
}
public static JTextField addTranslatableTextField(JPanel pane, String label, String initialValue, boolean editable, final FieldUpdateListener listener) {
final JTextField tfField = addTextField(pane, label, initialValue, editable, listener);
if (Workspace.activeWorkspace.settings.translatorLanguage.getCurrentValue() != null) {
final JLabel translateLinkLabel = new JLabel(getWeblateLabelLink(initialValue));
pane.add(translateLinkLabel, JideBoxLayout.FIX);
tfField.getDocument().addDocumentListener(new DocumentListener() {
@Override
public void removeUpdate(DocumentEvent e) {
translateLinkLabel.setText(getWeblateLabelLink(tfField.getText()));
translateLinkLabel.revalidate();
translateLinkLabel.repaint();
}
@Override
public void insertUpdate(DocumentEvent e) {
translateLinkLabel.setText(getWeblateLabelLink(tfField.getText()));
translateLinkLabel.revalidate();
translateLinkLabel.repaint();
}
@Override
public void changedUpdate(DocumentEvent e) {
translateLinkLabel.setText(getWeblateLabelLink(tfField.getText()));
translateLinkLabel.revalidate();
translateLinkLabel.repaint();
}
});
translateLinkLabel.setCursor(new Cursor(Cursor.HAND_CURSOR));
translateLinkLabel.addMouseListener(new MouseAdapter() {
@Override
public void mouseClicked(MouseEvent e) {
if (e.getButton() == MouseEvent.BUTTON1) {
try {
Desktop.getDesktop().browse(new URI(getWeblateLabelURI(tfField.getText())));
} catch (IOException e1) {
e1.printStackTrace();
} catch (URISyntaxException e1) {
e1.printStackTrace();
}
}
}
});
}
return tfField;
}
public static String getWeblateLabelLink(String text) {
return "<html><a href=\""+getWeblateLabelURI(text)+"\">Translate on weblate</a></html>";
}
public static String getWeblateLabelURI(String text) {
return "https://hosted.weblate.org/translate/andors-trail/game-content/"+Workspace.activeWorkspace.settings.translatorLanguage.getCurrentValue()+"/?checksum="+HashUtils.weblateHash(text, "");
}
public static JTextField addTextField(JPanel pane, String label, String initialValue, boolean editable, final FieldUpdateListener listener) {
JPanel tfPane = new JPanel();
tfPane.setLayout(new JideBoxLayout(tfPane, JideBoxLayout.LINE_AXIS, 6));
@@ -151,6 +210,51 @@ public abstract class Editor extends JPanel implements ProjectElementListener {
return tfField;
}
public static JTextArea addTranslatableTextArea(JPanel pane, String label, String initialValue, boolean editable, final FieldUpdateListener listener) {
final JTextArea tfArea = addTextArea(pane, label, initialValue, editable, listener);
if (Workspace.activeWorkspace.settings.translatorLanguage.getCurrentValue() != null) {
final JLabel translateLinkLabel = new JLabel(getWeblateLabelLink(initialValue));
pane.add(translateLinkLabel, JideBoxLayout.FIX);
tfArea.getDocument().addDocumentListener(new DocumentListener() {
@Override
public void removeUpdate(DocumentEvent e) {
translateLinkLabel.setText(getWeblateLabelLink(tfArea.getText().replaceAll("\n", Matcher.quoteReplacement("\n"))));
translateLinkLabel.revalidate();
translateLinkLabel.repaint();
}
@Override
public void insertUpdate(DocumentEvent e) {
translateLinkLabel.setText(getWeblateLabelLink(tfArea.getText().replaceAll("\n", Matcher.quoteReplacement("\n"))));
translateLinkLabel.revalidate();
translateLinkLabel.repaint();
}
@Override
public void changedUpdate(DocumentEvent e) {
translateLinkLabel.setText(getWeblateLabelLink(tfArea.getText().replaceAll("\n", Matcher.quoteReplacement("\n"))));
translateLinkLabel.revalidate();
translateLinkLabel.repaint();
}
});
translateLinkLabel.setCursor(new Cursor(Cursor.HAND_CURSOR));
translateLinkLabel.addMouseListener(new MouseAdapter() {
@Override
public void mouseClicked(MouseEvent e) {
if (e.getButton() == MouseEvent.BUTTON1) {
try {
Desktop.getDesktop().browse(new URI(getWeblateLabelURI(tfArea.getText().replaceAll("\n", Matcher.quoteReplacement("\n")))));
} catch (IOException e1) {
e1.printStackTrace();
} catch (URISyntaxException e1) {
e1.printStackTrace();
}
}
}
});
}
return tfArea;
}
public static JTextArea addTextArea(JPanel pane, String label, String initialValue, boolean editable, final FieldUpdateListener listener) {
String text= initialValue == null ? "" : initialValue.replaceAll("\\n", "\n");

View File

@@ -6,7 +6,10 @@ import java.awt.event.ActionListener;
import javax.swing.ButtonGroup;
import javax.swing.JButton;
import javax.swing.JCheckBox;
import javax.swing.JComboBox;
import javax.swing.JDialog;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JRadioButton;
import javax.swing.JScrollPane;
@@ -28,6 +31,9 @@ public class WorkspaceSettingsEditor extends JDialog {
JRadioButton useSystemDefaultImageViewerButton, useSystemDefaultImageEditorButton, useCustomImageEditorButton;
JTextField imageEditorCommandField;
JCheckBox translatorModeBox;
JComboBox<String> translatorLanguagesBox;
public WorkspaceSettingsEditor(WorkspaceSettings settings) {
@@ -46,6 +52,7 @@ public class WorkspaceSettingsEditor extends JDialog {
pane.add(getExternalToolsPane(), JideBoxLayout.FIX);
pane.add(getTranslatorModePane(), JideBoxLayout.FIX);
pane.add(new JPanel(), JideBoxLayout.VARY);
buttonPane.add(new JPanel(), JideBoxLayout.VARY);
@@ -147,6 +154,32 @@ public class WorkspaceSettingsEditor extends JDialog {
return pane;
}
public JPanel getTranslatorModePane() {
CollapsiblePanel pane = new CollapsiblePanel("Translator options");
pane.setLayout(new JideBoxLayout(pane, JideBoxLayout.PAGE_AXIS));
translatorModeBox = new JCheckBox("Activate translator mode");
pane.add(translatorModeBox, JideBoxLayout.FIX);
JPanel langPane = new JPanel();
langPane.setLayout(new JideBoxLayout(langPane, JideBoxLayout.LINE_AXIS));
langPane.add(new JLabel("Language code: "), JideBoxLayout.FIX);
translatorLanguagesBox = new JComboBox<String>(WorkspaceSettings.LANGUAGE_LIST);
langPane.add(translatorLanguagesBox);
pane.add(langPane, JideBoxLayout.FIX);
translatorModeBox.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
translatorLanguagesBox.setEnabled(translatorModeBox.isSelected());
}
});
pane.add(new JLabel("If your language isn't here, complain on the forums at https://andorstrail.com/"), JideBoxLayout.FIX);
return pane;
}
public void loadFromModel() {
//Tiled
useSystemDefaultMapEditorButton.setSelected(settings.useSystemDefaultMapEditor.getCurrentValue());
@@ -157,6 +190,14 @@ public class WorkspaceSettingsEditor extends JDialog {
useSystemDefaultImageEditorButton.setSelected(settings.useSystemDefaultImageEditor.getCurrentValue());
useCustomImageEditorButton.setSelected(!(settings.useSystemDefaultImageViewer.getCurrentValue() || settings.useSystemDefaultImageEditor.getCurrentValue()));
imageEditorCommandField.setText(settings.imageEditorCommand.getCurrentValue());
//Translator
if (settings.translatorLanguage.getCurrentValue() != null) {
translatorModeBox.setSelected(true);
translatorLanguagesBox.setSelectedItem(settings.translatorLanguage.getCurrentValue());
} else {
translatorModeBox.setSelected(false);
translatorLanguagesBox.setSelectedItem(null);
}
}
public void pushToModel() {
@@ -167,7 +208,12 @@ public class WorkspaceSettingsEditor extends JDialog {
settings.useSystemDefaultImageViewer.setCurrentValue(useSystemDefaultImageViewerButton.isSelected());
settings.useSystemDefaultImageEditor.setCurrentValue(useSystemDefaultImageEditorButton.isSelected());
settings.imageEditorCommand.setCurrentValue(imageEditorCommandField.getText());
//Translator
if (translatorModeBox.isSelected()) {
settings.translatorLanguage.setCurrentValue((String)translatorLanguagesBox.getSelectedItem());
} else {
settings.translatorLanguage.resetDefault();
}
settings.save();
}

View File

@@ -76,7 +76,7 @@ public class ActorConditionEditor extends JSONElementEditor {
acIcon = createButtonPane(pane, ac.getProject(), ac, ActorCondition.class, ac.getImage(), Spritesheet.Category.actorcondition, listener);
idField = addTextField(pane, "Internal ID: ", ac.id, ac.writable, listener);
nameField = addTextField(pane, "Display name: ", ac.display_name, ac.writable, listener);
nameField = addTranslatableTextField(pane, "Display name: ", ac.display_name, ac.writable, listener);
categoryBox = addEnumValueBox(pane, "Category: ", ActorCondition.ACCategory.values(), ac.category, ac.writable, listener);
positiveBox = addIntegerBasedCheckBox(pane, "Positive", ac.positive, ac.writable, listener);
stackingBox = addIntegerBasedCheckBox(pane, "Stacking", ac.stacking, ac.writable, listener);

View File

@@ -165,7 +165,7 @@ public class DialogueEditor extends JSONElementEditor {
createButtonPane(pane, dialogue.getProject(), dialogue, Dialogue.class, dialogue.getImage(), null, listener);
idField = addTextField(pane, "Internal ID: ", dialogue.id, dialogue.writable, listener);
messageField = addTextArea(pane, "Message: ", dialogue.message, dialogue.writable, listener);
messageField = addTranslatableTextArea(pane, "Message: ", dialogue.message, dialogue.writable, listener);
switchToNpcBox = addNPCBox(pane, dialogue.getProject(), "Switch active NPC to: ", dialogue.switch_to_npc, dialogue.writable, listener);
CollapsiblePanel rewards = new CollapsiblePanel("Reaching this phrase gives the following rewards: ");

View File

@@ -52,7 +52,7 @@ public class ItemCategoryEditor extends JSONElementEditor {
idField = addTextField(pane, "Internal ID: ", ic.id, ic.writable, listener);
nameField = addTextField(pane, "Display name: ", ic.name, ic.writable, listener);
nameField = addTranslatableTextField(pane, "Display name: ", ic.name, ic.writable, listener);
typeBox = addEnumValueBox(pane, "Action type: ", ItemCategory.ActionType.values(), ic.action_type, ic.writable, listener);
slotBox = addEnumValueBox(pane, "Inventory slot: ", ItemCategory.InventorySlot.values(), ic.slot, ic.writable, listener);
sizeBox = addEnumValueBox(pane, "Item size: ", ItemCategory.Size.values(), ic.size, ic.writable, listener);

View File

@@ -149,8 +149,8 @@ public class ItemEditor extends JSONElementEditor {
itemIcon = createButtonPane(pane, item.getProject(), item, Item.class, item.getImage(), Spritesheet.Category.item, listener);
idField = addTextField(pane, "Internal ID: ", item.id, item.writable, listener);
nameField = addTextField(pane, "Display name: ", item.name, item.writable, listener);
descriptionField = addTextField(pane, "Description: ", item.description, item.writable, listener);
nameField = addTranslatableTextField(pane, "Display name: ", item.name, item.writable, listener);
descriptionField = addTranslatableTextField(pane, "Description: ", item.description, item.writable, listener);
typeBox = addEnumValueBox(pane, "Type: ", Item.DisplayType.values(), item.display_type, item.writable, listener);
manualPriceBox = addIntegerBasedCheckBox(pane, "Has manual price", item.has_manual_price, item.writable, listener);
baseManualPrice = item.base_market_cost;

View File

@@ -171,7 +171,7 @@ public class NPCEditor extends JSONElementEditor {
npcIcon = createButtonPane(pane, npc.getProject(), npc, NPC.class, npc.getImage(), Spritesheet.Category.monster, listener);
idField = addTextField(pane, "Internal ID: ", npc.id, npc.writable, listener);
nameField = addTextField(pane, "Display name: ", npc.name, npc.writable, listener);
nameField = addTranslatableTextField(pane, "Display name: ", npc.name, npc.writable, listener);
spawnGroupField = addTextField(pane, "Spawn group ID: ", npc.spawngroup_id, npc.writable, listener);
experienceField = addIntegerField(pane, "Experience reward: ", npc.getMonsterExperience(), false, false, listener);
dialogueBox = addDialogueBox(pane, npc.getProject(), "Initial phrase: ", npc.dialogue, npc.writable, listener);

View File

@@ -69,7 +69,7 @@ public class QuestEditor extends JSONElementEditor {
idField = addTextField(pane, "Internal ID: ", quest.id, quest.writable, listener);
nameField = addTextField(pane, "Quest Name: ", quest.name, quest.writable, listener);
nameField = addTranslatableTextField(pane, "Quest Name: ", quest.name, quest.writable, listener);
visibleBox = addIntegerBasedCheckBox(pane, "Visible in quest log", quest.visible_in_log, quest.writable, listener);
JPanel stagesPane = new JPanel();

View File

@@ -0,0 +1,60 @@
package com.gpl.rpg.atcontentstudio.utils;
import java.io.UnsupportedEncodingException;
import java.util.LinkedHashMap;
import java.util.Map;
import com.zackehh.siphash.SipHash;
import com.zackehh.siphash.SipHashResult;
public class HashUtils {
private static final String WEBLATE_SIPASH_KEY = "Weblate Sip Hash";
private static final Map<String, SipHash> HASHER_CACHE = new LinkedHashMap<String, SipHash>();
public static String weblateHash(String str, String ctx) {
byte[] data = null;
if (str != null) {
byte[] strBytes;
try {
strBytes = str.getBytes("UTF-8");
byte[] ctxBytes = ctx.getBytes("UTF-8");
data = new byte[strBytes.length + ctxBytes.length];
System.arraycopy(strBytes, 0, data, 0, strBytes.length);
System.arraycopy(ctxBytes, 0, data, strBytes.length, ctxBytes.length);
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
} else {
try {
data = ctx.getBytes("UTF-8");
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
}
return siphash(WEBLATE_SIPASH_KEY, data);
}
private static String siphash(String key, byte[] data) {
SipHash hasher = HASHER_CACHE.get(key);
if (hasher == null) {
hasher= new SipHash("Weblate Sip Hash".getBytes());
HASHER_CACHE.put(key, hasher);
}
if (data != null) {
SipHashResult result = hasher.hash(data);
return result.getHex();
}
return null;
}
}