diff --git a/src/main/java/the/bytecode/club/bytecodeviewer/Configuration.java b/src/main/java/the/bytecode/club/bytecodeviewer/Configuration.java index 9a776d77..85dbb4b3 100644 --- a/src/main/java/the/bytecode/club/bytecodeviewer/Configuration.java +++ b/src/main/java/the/bytecode/club/bytecodeviewer/Configuration.java @@ -2,6 +2,7 @@ package the.bytecode.club.bytecodeviewer; import the.bytecode.club.bytecodeviewer.gui.theme.LAFTheme; import the.bytecode.club.bytecodeviewer.gui.theme.RSTATheme; +import the.bytecode.club.bytecodeviewer.translation.Language; import java.io.File; @@ -37,6 +38,8 @@ public class Configuration public static long lastHotKeyExecuted = System.currentTimeMillis(); + public static Language language = Language.ENGLISH; + public static LAFTheme lafTheme = LAFTheme.SYSTEM; //lightmode by default since it uses the system theme public static RSTATheme rstaTheme = lafTheme.getRSTATheme(); } \ No newline at end of file diff --git a/src/main/java/the/bytecode/club/bytecodeviewer/gui/MainViewerGUI.java b/src/main/java/the/bytecode/club/bytecodeviewer/gui/MainViewerGUI.java index 434d2242..f971dc07 100644 --- a/src/main/java/the/bytecode/club/bytecodeviewer/gui/MainViewerGUI.java +++ b/src/main/java/the/bytecode/club/bytecodeviewer/gui/MainViewerGUI.java @@ -26,8 +26,13 @@ import the.bytecode.club.bytecodeviewer.obfuscators.rename.RenameMethods; import the.bytecode.club.bytecodeviewer.plugin.PluginManager; import the.bytecode.club.bytecodeviewer.plugin.preinstalled.*; import the.bytecode.club.bytecodeviewer.resources.exporting.Export; +import the.bytecode.club.bytecodeviewer.translation.components.TranslatedJCheckBoxMenuItem; import the.bytecode.club.bytecodeviewer.util.*; import the.bytecode.club.bytecodeviewer.resources.ResourceDecompiling; +import the.bytecode.club.bytecodeviewer.translation.Language; +import the.bytecode.club.bytecodeviewer.translation.Translation; +import the.bytecode.club.bytecodeviewer.translation.components.TranslatedJMenu; +import the.bytecode.club.bytecodeviewer.translation.components.TranslatedJMenuItem; import static the.bytecode.club.bytecodeviewer.Constants.*; @@ -72,78 +77,80 @@ public class MainViewerGUI extends JFrame public final JMenuBar rootMenu = new JMenuBar(); //all of the files main menu components - public final JMenu fileMainMenu = new JMenu("File"); - public final JMenuItem newWorkSpace = new JMenuItem("New Workspace"); - public final JMenuItem addResource = new JMenuItem("Add..."); - public final JMenuItem reloadResources = new JMenuItem("Reload Resources"); - public final JMenuItem runButton = new JMenuItem("Run"); - public final JMenuItem compileButton = new JMenuItem("Compile"); - public final JMenuItem saveAsRunnableJar = new JMenuItem("Save As Runnable Jar.."); - public final JMenuItem saveAsDex = new JMenuItem("Save As DEX.."); - public final JMenuItem saveAsAPK = new JMenuItem("Save As APK.."); - public final JMenuItem saveAsZip = new JMenuItem("Save As Zip.."); - public final JMenuItem decompileSaveOpened = new JMenuItem("Decompile & Save Opened Class.."); - public final JMenuItem decompileSaveAll = new JMenuItem("Decompile & Save All Classes.."); - public final JMenu recentFilesSecondaryMenu = new JMenu("Recent Files"); - public final JMenuItem about = new JMenuItem("About"); - public final JMenuItem exit = new JMenuItem("Exit"); + public final JMenu fileMainMenu = new TranslatedJMenu("File", Translation.FILE); + public final JMenuItem addResource = new TranslatedJMenuItem("Add...", Translation.ADD); + public final JMenuItem newWorkSpace = new TranslatedJMenuItem("New Workspace", Translation.NEW_WORKSPACE); + public final JMenuItem reloadResources = new TranslatedJMenuItem("Reload Resources", Translation.RELOAD_RESOURCES); + public final JMenuItem runButton = new TranslatedJMenuItem("Run", Translation.RUN); + public final JMenuItem compileButton = new TranslatedJMenuItem("Compile", Translation.COMPILE); + public final JMenuItem saveAsRunnableJar = new TranslatedJMenuItem("Save As Runnable Jar..", Translation.SAVE_AS_RUNNABLE_JAR); + public final JMenuItem saveAsDex = new TranslatedJMenuItem("Save As DEX..", Translation.SAVE_AS_DEX); + public final JMenuItem saveAsAPK = new TranslatedJMenuItem("Save As APK..", Translation.SAVE_AS_APK); + public final JMenuItem saveAsZip = new TranslatedJMenuItem("Save As Zip..", Translation.SAVE_AS_ZIP); + public final JMenuItem decompileSaveOpened = new TranslatedJMenuItem("Decompile & Save Opened Class..", Translation.DECOMPILE_SAVE_OPENED_CLASSES); + public final JMenuItem decompileSaveAll = new TranslatedJMenuItem("Decompile & Save All Classes..", Translation.DECOMPILE_SAVE_ALL_CLASSES); + public final JMenu recentFilesSecondaryMenu = new TranslatedJMenu("Recent Files", Translation.RECENT_FILES); + public final JMenuItem about = new TranslatedJMenuItem("About", Translation.ABOUT); + public final JMenuItem exit = new TranslatedJMenuItem("Exit", Translation.EXIT); //all of the view main menu components - public final JMenu viewMainMenu = new JMenu("View"); + public final JMenu viewMainMenu = new TranslatedJMenu("View", Translation.VIEW); public final DecompilerSelectionPane viewPane1 = new DecompilerSelectionPane(1); public final DecompilerSelectionPane viewPane2 = new DecompilerSelectionPane(2); public final DecompilerSelectionPane viewPane3 = new DecompilerSelectionPane(3); //all of the plugins main menu components - public final JMenu pluginsMainMenu = new JMenu("Plugins"); - public final JMenuItem openExternalPlugin = new JMenuItem("Open Plugin..."); - public final JMenu recentPluginsSecondaryMenu = new JMenu("Recent Plugins"); - public final JMenuItem ZKMStringDecrypter = new JMenuItem("ZKM String Decrypter"); - public final JMenuItem allatoriStringDecrypter = new JMenuItem("Allatori String Decrypter"); - public final JMenuItem codeSequenceDiagram = new JMenuItem("Code Sequence Diagram"); - public final JMenuItem maliciousCodeScanner = new JMenuItem("Malicious Code Scanner"); - public final JMenuItem showAllStrings = new JMenuItem("Show All Strings"); - public final JMenuItem showMainMethods = new JMenuItem("Show Main Methods"); - public final JMenuItem replaceStrings = new JMenuItem("Replace Strings"); - public final JMenuItem stackFramesRemover = new JMenuItem("StackFrames Remover"); - public final JMenuItem zStringArrayDecrypter = new JMenuItem("ZStringArray Decrypter"); + public final JMenu pluginsMainMenu = new TranslatedJMenu("Plugins", Translation.PLUGINS); + public final JMenuItem openExternalPlugin = new TranslatedJMenuItem("Open Plugin...", Translation.OPEN_PLUGIN); + public final JMenu recentPluginsSecondaryMenu = new TranslatedJMenu("Recent Plugins", Translation.RECENT_PLUGINS); + public final JMenuItem codeSequenceDiagram = new TranslatedJMenuItem("Code Sequence Diagram", Translation.CODE_SEQUENCE_DIAGRAM); + public final JMenuItem maliciousCodeScanner = new TranslatedJMenuItem("Malicious Code Scanner", Translation.MALICIOUS_CODE_SCANNER); + public final JMenuItem showAllStrings = new TranslatedJMenuItem("Show All Strings", Translation.SHOW_ALL_STRINGS); + public final JMenuItem showMainMethods = new TranslatedJMenuItem("Show Main Methods", Translation.SHOW_MAIN_METHODS); + public final JMenuItem replaceStrings = new TranslatedJMenuItem("Replace Strings", Translation.REPLACE_STRINGS); + public final JMenuItem stackFramesRemover = new TranslatedJMenuItem("StackFrames Remover", Translation.STACK_FRAMES_REMOVER); + public final JMenuItem ZKMStringDecrypter = new TranslatedJMenuItem("ZKM String Decrypter", Translation.ZKM_STRING_DECRYPTER); + public final JMenuItem allatoriStringDecrypter = new TranslatedJMenuItem("Allatori String Decrypter", Translation.ALLATORI_STRING_DECRYPTER); + public final JMenuItem zStringArrayDecrypter = new TranslatedJMenuItem("ZStringArray Decrypter", Translation.ZSTRINGARRAY_DECRYPTER); //all of the settings main menu components public final ButtonGroup apkConversionGroup = new ButtonGroup(); public final JRadioButtonMenuItem apkConversionDex = new JRadioButtonMenuItem("Dex2Jar"); public final JRadioButtonMenuItem apkConversionEnjarify = new JRadioButtonMenuItem("Enjarify"); - public final JMenu fontSize = new JMenu("Font Size"); + public final JMenu rstaTheme = new TranslatedJMenu("Text Area Theme", Translation.TEXT_AREA_THEME); + public final JMenu lafTheme = new TranslatedJMenu("Window Theme", Translation.WINDOW_THEME); + public final JMenu language = new TranslatedJMenu("Language", Translation.LANGUAGE); + public final JMenu fontSize = new TranslatedJMenu("Font Size", Translation.FONT_SIZE); public final JSpinner fontSpinner = new JSpinner(); - public final JMenu rstaTheme = new JMenu("Text Area Theme"); - public final JMenu lafTheme = new JMenu("Window Theme"); - - //BCV settings - public final JCheckBoxMenuItem refreshOnChange = new JCheckBoxMenuItem("Refresh On View Change"); - private final JCheckBoxMenuItem deleteForeignOutdatedLibs = new JCheckBoxMenuItem("Delete Foreign/Outdated Libs"); - public final JMenu settingsMainMenu = new JMenu("Settings"); - public final JMenu visualSettings = new JMenu("Visual Settings"); - public final JMenu apkConversion = new JMenu("APK Conversion"); - public final JMenu bytecodeDecompilerSettingsSecondaryMenu = new JMenu("Bytecode Decompiler"); - public final JCheckBoxMenuItem updateCheck = new JCheckBoxMenuItem("Update Check"); - public final JMenuItem setPython2 = new JMenuItem("Set Python 2.7 Executable"); - public final JMenuItem setPython3 = new JMenuItem("Set Python 3.X Executable"); - public final JMenuItem setJRERT = new JMenuItem("Set JRE RT Library"); - public final JMenuItem setJavac = new JMenuItem("Set Javac Executable"); - public final JMenuItem setOptionalLibrary = new JMenuItem("Set Optional Library Folder"); - public final JCheckBoxMenuItem compileOnSave = new JCheckBoxMenuItem("Compile On Save"); - public final JCheckBoxMenuItem showFileInTabTitle = new JCheckBoxMenuItem("Show File In Tab Title"); - public final JCheckBoxMenuItem simplifyNameInTabTitle = new JCheckBoxMenuItem("Simplify Name In Tab Title"); - public final JCheckBoxMenuItem forcePureAsciiAsText = new JCheckBoxMenuItem("Force Pure Ascii As Text"); - public final JCheckBoxMenuItem autoCompileOnRefresh = new JCheckBoxMenuItem("Compile On Refresh"); - public final JCheckBoxMenuItem decodeAPKResources = new JCheckBoxMenuItem("Decode APK Resources"); - public final JCheckBoxMenuItem synchronizedViewing = new JCheckBoxMenuItem("Synchronized Viewing"); - public final JCheckBoxMenuItem showClassMethods = new JCheckBoxMenuItem("Show Class Methods"); public final Map rstaThemes = new HashMap<>(); public final Map lafThemes = new HashMap<>(); + public final Map languages = new HashMap<>(); + + //BCV settings + public final JCheckBoxMenuItem refreshOnChange = new TranslatedJCheckBoxMenuItem("Refresh On View Change", Translation.REFRESH_ON_VIEW_CHANGE); + private final JCheckBoxMenuItem deleteForeignOutdatedLibs = new TranslatedJCheckBoxMenuItem("Delete Foreign/Outdated Libs", Translation.DELETE_UNKNOWN_LIBS); + public final JMenu settingsMainMenu = new TranslatedJMenu("Settings", Translation.SETTINGS); + public final JMenu visualSettings = new TranslatedJMenu("Visual Settings", Translation.VISUAL_SETTINGS); + public final JMenu apkConversion = new TranslatedJMenu("APK Conversion", Translation.APK_CONVERSION); + public final JMenu bytecodeDecompilerSettingsSecondaryMenu = new TranslatedJMenu("Bytecode Decompiler", Translation.BYTECODE_DECOMPILER); + public final JCheckBoxMenuItem updateCheck = new TranslatedJCheckBoxMenuItem("Update Check", Translation.UPDATE_CHECK); + public final JMenuItem setPython2 = new TranslatedJMenuItem("Set Python 2.7 Executable", Translation.SET_PYTHON_27_EXECUTABLE); + public final JMenuItem setPython3 = new TranslatedJMenuItem("Set Python 3.X Executable", Translation.SET_PYTHON_30_EXECUTABLE); + public final JMenuItem setJRERT = new TranslatedJMenuItem("Set JRE RT Library", Translation.SET_JRE_RT_LIBRARY); + public final JMenuItem setJavac = new TranslatedJMenuItem("Set Javac Executable", Translation.SET_JAVAC_EXECUTABLE); + public final JMenuItem setOptionalLibrary = new TranslatedJMenuItem("Set Optional Library Folder", Translation.SET_OPTIONAL_LIBRARY_FOLDER); + public final JCheckBoxMenuItem compileOnSave = new TranslatedJCheckBoxMenuItem("Compile On Save", Translation.COMPILE_ON_SAVE); + public final JCheckBoxMenuItem showFileInTabTitle = new TranslatedJCheckBoxMenuItem("Show File In Tab Title", Translation.SHOW_TAB_FILE_IN_TAB_TITLE); + public final JCheckBoxMenuItem simplifyNameInTabTitle = new TranslatedJCheckBoxMenuItem("Simplify Name In Tab Title", Translation.SIMPLIFY_NAME_IN_TAB_TITLE); + public final JCheckBoxMenuItem forcePureAsciiAsText = new TranslatedJCheckBoxMenuItem("Force Pure Ascii As Text", Translation.FORCE_PURE_ASCII_AS_TEXT); + public final JCheckBoxMenuItem autoCompileOnRefresh = new TranslatedJCheckBoxMenuItem("Compile On Refresh", Translation.COMPILE_ON_REFRESH); + public final JCheckBoxMenuItem decodeAPKResources = new TranslatedJCheckBoxMenuItem("Decode APK Resources", Translation.DECODE_APK_RESOURCES); + public final JCheckBoxMenuItem synchronizedViewing = new TranslatedJCheckBoxMenuItem("Synchronized Viewing", Translation.SYNCHRONIZED_VIEWING); + public final JCheckBoxMenuItem showClassMethods = new TranslatedJCheckBoxMenuItem("Show Class Methods", Translation.SHOW_CLASS_METHODS); //CFIDE settings - public final JCheckBoxMenuItem appendBracketsToLabels = new JCheckBoxMenuItem("Append Brackets To Labels"); - public JCheckBoxMenuItem debugHelpers = new JCheckBoxMenuItem("Debug Helpers"); + public final JCheckBoxMenuItem appendBracketsToLabels = new TranslatedJCheckBoxMenuItem("Append Brackets To Labels", Translation.APPEND_BRACKETS_TO_LABEL); + public JCheckBoxMenuItem debugHelpers = new TranslatedJCheckBoxMenuItem("Debug Helpers", Translation.DEBUG_HELPERS); //FernFlower settings public final JMenu fernFlowerSettingsSecondaryMenu = new JMenu("FernFlower"); @@ -399,7 +406,7 @@ public class MainViewerGUI extends JFrame { Configuration.rstaTheme = t; item.setSelected(true); - Settings.saveSettings(); + SettingsSerializer.saveSettings(); }); rstaThemes.put(t, item); @@ -421,7 +428,7 @@ public class MainViewerGUI extends JFrame Configuration.rstaTheme = theme.getRSTATheme(); rstaThemes.get(Configuration.rstaTheme).setSelected(true); item.setSelected(true); - Settings.saveSettings(); + SettingsSerializer.saveSettings(); try { @@ -437,9 +444,29 @@ public class MainViewerGUI extends JFrame lafThemes.put(theme, item); lafTheme.add(item); } + + ButtonGroup languageGroup = new ButtonGroup(); + for (Language l : Language.values()) + { + JRadioButtonMenuItem item = new JRadioButtonMenuItem(l.getReadableName()); + if (Configuration.language.equals(l)) + item.setSelected(true); + + languageGroup.add(item); + + item.addActionListener(e -> + { + SettingsSerializer.saveSettings(); + MiscUtils.setLanguage(l); + }); + + languages.put(l, item); + language.add(item); + } visualSettings.add(lafTheme); visualSettings.add(rstaTheme); + visualSettings.add(language); visualSettings.add(fontSize); visualSettings.add(showFileInTabTitle); visualSettings.add(simplifyNameInTabTitle); @@ -538,7 +565,7 @@ public class MainViewerGUI extends JFrame bytecodeDecompilerSettingsSecondaryMenu.add(appendBracketsToLabels); deleteForeignOutdatedLibs.addActionListener(arg0 -> showForeignLibraryWarning()); - forcePureAsciiAsText.addActionListener(arg0 -> Settings.saveSettings()); + forcePureAsciiAsText.addActionListener(arg0 -> SettingsSerializer.saveSettings()); setPython2.addActionListener(arg0 -> selectPythonC()); setJRERT.addActionListener(arg0 -> selectJRERTLibrary()); setPython3.addActionListener(arg0 -> selectPythonC3()); @@ -546,12 +573,12 @@ public class MainViewerGUI extends JFrame setJavac.addActionListener(arg0 -> selectJavac()); showFileInTabTitle.addActionListener(arg0 -> { Configuration.displayParentInTab = BytecodeViewer.viewer.showFileInTabTitle.isSelected(); - Settings.saveSettings(); + SettingsSerializer.saveSettings(); BytecodeViewer.refreshAllTabTitles(); }); simplifyNameInTabTitle.addActionListener(arg0 -> { Configuration.simplifiedTabNames = BytecodeViewer.viewer.simplifyNameInTabTitle.isSelected(); - Settings.saveSettings(); + SettingsSerializer.saveSettings(); BytecodeViewer.refreshAllTabTitles(); }); } diff --git a/src/main/java/the/bytecode/club/bytecodeviewer/gui/components/DecompilerViewComponent.java b/src/main/java/the/bytecode/club/bytecodeviewer/gui/components/DecompilerViewComponent.java index 135dd78e..b88f9b0d 100644 --- a/src/main/java/the/bytecode/club/bytecodeviewer/gui/components/DecompilerViewComponent.java +++ b/src/main/java/the/bytecode/club/bytecodeviewer/gui/components/DecompilerViewComponent.java @@ -1,5 +1,7 @@ package the.bytecode.club.bytecodeviewer.gui.components; +import the.bytecode.club.bytecodeviewer.translation.Translation; +import the.bytecode.club.bytecodeviewer.translation.components.TranslatedJCheckBoxMenuItem; import the.bytecode.club.bytecodeviewer.util.RefreshWorkPane; import javax.swing.*; @@ -33,7 +35,7 @@ public class DecompilerViewComponent private final JMenu menu; private final JRadioButtonMenuItem java = new JRadioButtonMenuItem("Java"); private final JRadioButtonMenuItem bytecode = new JRadioButtonMenuItem("Bytecode"); - private final JCheckBoxMenuItem editable = new JCheckBoxMenuItem("Editable"); + private final JCheckBoxMenuItem editable = new TranslatedJCheckBoxMenuItem("Editable", Translation.EDITABLE); public DecompilerViewComponent(String name) { this(name, false); diff --git a/src/main/java/the/bytecode/club/bytecodeviewer/gui/resourceviewer/DecompilerSelectionPane.java b/src/main/java/the/bytecode/club/bytecodeviewer/gui/resourceviewer/DecompilerSelectionPane.java index d3f00d72..2190f22f 100644 --- a/src/main/java/the/bytecode/club/bytecodeviewer/gui/resourceviewer/DecompilerSelectionPane.java +++ b/src/main/java/the/bytecode/club/bytecodeviewer/gui/resourceviewer/DecompilerSelectionPane.java @@ -2,6 +2,9 @@ package the.bytecode.club.bytecodeviewer.gui.resourceviewer; import the.bytecode.club.bytecodeviewer.decompilers.Decompiler; import the.bytecode.club.bytecodeviewer.gui.components.DecompilerViewComponent; +import the.bytecode.club.bytecodeviewer.translation.Translation; +import the.bytecode.club.bytecodeviewer.translation.components.TranslatedJMenu; +import the.bytecode.club.bytecodeviewer.translation.components.TranslatedJRadioButtonMenuItem; import javax.swing.*; @@ -14,7 +17,7 @@ public class DecompilerSelectionPane public final int paneID; public final JMenu menu; public final ButtonGroup group = new ButtonGroup(); - public final JRadioButtonMenuItem none = new JRadioButtonMenuItem("None"); + public final JRadioButtonMenuItem none = new TranslatedJRadioButtonMenuItem("None", Translation.NONE); public final DecompilerViewComponent procyon = new DecompilerViewComponent("Procyon"); public final DecompilerViewComponent CFR = new DecompilerViewComponent("CFR"); public final DecompilerViewComponent JADX = new DecompilerViewComponent("JADX"); @@ -28,7 +31,12 @@ public class DecompilerSelectionPane public DecompilerSelectionPane(int paneID) { this.paneID = paneID; - this.menu = new JMenu("Pane " + paneID); + if(paneID == 1) + this.menu = new TranslatedJMenu("Pane " + paneID, Translation.PANE_1); + else if(paneID == 2) + this.menu = new TranslatedJMenu("Pane " + paneID, Translation.PANE_2); + else + this.menu = new TranslatedJMenu("Pane " + paneID, Translation.PANE_3); buildMenu(); } diff --git a/src/main/java/the/bytecode/club/bytecodeviewer/translation/Language.java b/src/main/java/the/bytecode/club/bytecodeviewer/translation/Language.java new file mode 100644 index 00000000..ce952226 --- /dev/null +++ b/src/main/java/the/bytecode/club/bytecodeviewer/translation/Language.java @@ -0,0 +1,125 @@ +package the.bytecode.club.bytecodeviewer.translation; + +import com.google.gson.reflect.TypeToken; +import org.apache.commons.collections4.CollectionUtils; +import org.apache.commons.collections4.map.HashedMap; +import org.apache.commons.collections4.map.LinkedMap; +import org.apache.commons.io.IOUtils; +import the.bytecode.club.bytecodeviewer.BytecodeViewer; +import the.bytecode.club.bytecodeviewer.Resources; + +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.util.Arrays; +import java.util.HashMap; +import java.util.HashSet; + +/** + * All of the supported languages + * + * @author Konloch + * @since 6/28/2021 + */ +public enum Language +{ + ENGLISH("/translations/english.json", "English", "en"), + MANDARIN("/translations/mandarin.json", "普通话", "zh_cn", "zh"), + //HINDI("/translations/hindi.json", "hi"), + /*SPANISH("/translations/spanish.json", "es"), + FRENCH("/translations/french.json", "fr"), + ARABIC("/translations/arabic.json", "ab"), + RUSSIAN("/translations/russian.json", "ru"),*/ + GERMAN("/translations/german.json", "Deutsche", "de"), + ; + + private static final HashedMap languageCodeLookup; + + static + { + languageCodeLookup = new HashedMap<>(); + for(Language l : values()) + l.languageCode.forEach((langCode)-> + languageCodeLookup.put(langCode, l)); + } + + private final String resourcePath; + private final String readableName; + private final HashSet languageCode; + + Language(String resourcePath, String readableName, String... languageCodes) + { + this.resourcePath = resourcePath; + this.languageCode = new HashSet<>(Arrays.asList(languageCodes)); + this.readableName = readableName; + } + + public void loadLanguage() throws IOException + { + HashMap translationMap = BytecodeViewer.gson.fromJson( + IOUtils.toString(Resources.class.getResourceAsStream(resourcePath), StandardCharsets.UTF_8), + new TypeToken>(){}.getType()); + + for(Translation translation : Translation.values()) + { + TranslatedComponent text = translation.getTranslatedComponent(); + + //skip translating if the language config is missing the translation key + if(!translationMap.containsKey(text.key)) + continue; + + //update translation text value + text.value = translationMap.get(text.key); + + //check if translation key has been assigned to a component, + //on fail print an error alerting the devs + if(translation.getTranslatedComponent().runOnUpdate.isEmpty()) + { + System.err.println("Translation Reference " + translation.name() + " is missing component attachment, skipping..."); + continue; + } + + //trigger translation event + translation.getTranslatedComponent().runOnUpdate.forEach(Runnable::run); + } + } + + //TODO + // When adding new Translation Components: + // 1) start by adding the strings into the english.json + // 2) run this function to get the keys and add them into Translation.java + // 3) replace the swing component (MainViewerGUI) with a translated component + // and reference the correct translation key + // 4) add the translation key to the rest of the translation files + public void printLanguageKeys() throws IOException + { + if(this != ENGLISH) + return; + + LinkedMap translationMap = BytecodeViewer.gson.fromJson( + IOUtils.toString(Resources.class.getResourceAsStream(resourcePath), StandardCharsets.UTF_8), + new TypeToken>(){}.getType()); + + for(String key : translationMap.keySet()) + System.out.println(key + ","); + } + + public String getResourcePath() + { + return resourcePath; + } + + public HashSet getLanguageCode() + { + return languageCode; + } + + public String getReadableName() + { + return readableName; + } + + public static HashedMap getLanguageCodeLookup() + { + return languageCodeLookup; + } +} diff --git a/src/main/java/the/bytecode/club/bytecodeviewer/translation/TranslatedComponent.java b/src/main/java/the/bytecode/club/bytecodeviewer/translation/TranslatedComponent.java new file mode 100644 index 00000000..18b3e0f5 --- /dev/null +++ b/src/main/java/the/bytecode/club/bytecodeviewer/translation/TranslatedComponent.java @@ -0,0 +1,15 @@ +package the.bytecode.club.bytecodeviewer.translation; + +import java.util.ArrayList; +import java.util.List; + +/** + * @author Konloch + * @since 6/28/2021 + */ +public class TranslatedComponent +{ + public String key; + public String value; + public List runOnUpdate = new ArrayList<>(); +} diff --git a/src/main/java/the/bytecode/club/bytecodeviewer/translation/Translation.java b/src/main/java/the/bytecode/club/bytecodeviewer/translation/Translation.java new file mode 100644 index 00000000..c8030968 --- /dev/null +++ b/src/main/java/the/bytecode/club/bytecodeviewer/translation/Translation.java @@ -0,0 +1,89 @@ +package the.bytecode.club.bytecodeviewer.translation; + +/** + * All of the specific translations strings needed for BCV + * + * @author Konloch + * @since 6/28/2021 + */ +public enum Translation +{ + FILE, + ADD, + NEW_WORKSPACE, + RELOAD_RESOURCES, + RUN, + COMPILE, + SAVE_AS_RUNNABLE_JAR, + SAVE_AS_ZIP, + SAVE_AS_DEX, + SAVE_AS_APK, + DECOMPILE_SAVE_OPENED_CLASSES, + DECOMPILE_SAVE_ALL_CLASSES, + RECENT_FILES, + ABOUT, + EXIT, + + VIEW, + VISUAL_SETTINGS, + LANGUAGE, + WINDOW_THEME, + TEXT_AREA_THEME, + FONT_SIZE, + SHOW_TAB_FILE_IN_TAB_TITLE, + SIMPLIFY_NAME_IN_TAB_TITLE, + SYNCHRONIZED_VIEWING, + SHOW_CLASS_METHODS, + + PANE_1, + PANE_2, + PANE_3, + NONE, + EDITABLE, + + SETTINGS, + COMPILE_ON_SAVE, + COMPILE_ON_REFRESH, + REFRESH_ON_VIEW_CHANGE, + DECODE_APK_RESOURCES, + APK_CONVERSION, + UPDATE_CHECK, + DELETE_UNKNOWN_LIBS, + FORCE_PURE_ASCII_AS_TEXT, + SET_PYTHON_27_EXECUTABLE, + SET_PYTHON_30_EXECUTABLE, + SET_JRE_RT_LIBRARY, + SET_OPTIONAL_LIBRARY_FOLDER, + SET_JAVAC_EXECUTABLE, + BYTECODE_DECOMPILER, + DEBUG_HELPERS, + APPEND_BRACKETS_TO_LABEL, + + PLUGINS, + OPEN_PLUGIN, + RECENT_PLUGINS, + CODE_SEQUENCE_DIAGRAM, + MALICIOUS_CODE_SCANNER, + SHOW_MAIN_METHODS, + SHOW_ALL_STRINGS, + REPLACE_STRINGS, + STACK_FRAMES_REMOVER, + ZKM_STRING_DECRYPTER, + ALLATORI_STRING_DECRYPTER, + ZSTRINGARRAY_DECRYPTER, + + ; + + private final TranslatedComponent component; + + Translation() + { + this.component = new TranslatedComponent(); + this.component.key = name(); + } + + public TranslatedComponent getTranslatedComponent() + { + return component; + } +} \ No newline at end of file diff --git a/src/main/java/the/bytecode/club/bytecodeviewer/translation/components/TranslatedJCheckBoxMenuItem.java b/src/main/java/the/bytecode/club/bytecodeviewer/translation/components/TranslatedJCheckBoxMenuItem.java new file mode 100644 index 00000000..e1ef2574 --- /dev/null +++ b/src/main/java/the/bytecode/club/bytecodeviewer/translation/components/TranslatedJCheckBoxMenuItem.java @@ -0,0 +1,27 @@ +package the.bytecode.club.bytecodeviewer.translation.components; + +import the.bytecode.club.bytecodeviewer.translation.TranslatedComponent; +import the.bytecode.club.bytecodeviewer.translation.Translation; + +import javax.swing.*; + +/** + * @author Konloch + * @since 6/28/2021 + */ +public class TranslatedJCheckBoxMenuItem extends JCheckBoxMenuItem +{ + private final TranslatedComponent component; + + public TranslatedJCheckBoxMenuItem(String text, Translation translation) + { + super(text); + this.component = translation.getTranslatedComponent(); + this.component.runOnUpdate.add(this::updateText); + } + + public void updateText() + { + setText(component.value); + } +} diff --git a/src/main/java/the/bytecode/club/bytecodeviewer/translation/components/TranslatedJMenu.java b/src/main/java/the/bytecode/club/bytecodeviewer/translation/components/TranslatedJMenu.java new file mode 100644 index 00000000..1b2b5b1d --- /dev/null +++ b/src/main/java/the/bytecode/club/bytecodeviewer/translation/components/TranslatedJMenu.java @@ -0,0 +1,27 @@ +package the.bytecode.club.bytecodeviewer.translation.components; + +import the.bytecode.club.bytecodeviewer.translation.TranslatedComponent; +import the.bytecode.club.bytecodeviewer.translation.Translation; + +import javax.swing.*; + +/** + * @author Konloch + * @since 6/28/2021 + */ +public class TranslatedJMenu extends JMenu +{ + private final TranslatedComponent component; + + public TranslatedJMenu(String text, Translation translation) + { + super(text); + this.component = translation.getTranslatedComponent(); + this.component.runOnUpdate.add(this::updateText); + } + + public void updateText() + { + setText(component.value); + } +} \ No newline at end of file diff --git a/src/main/java/the/bytecode/club/bytecodeviewer/translation/components/TranslatedJMenuItem.java b/src/main/java/the/bytecode/club/bytecodeviewer/translation/components/TranslatedJMenuItem.java new file mode 100644 index 00000000..22c8f9b7 --- /dev/null +++ b/src/main/java/the/bytecode/club/bytecodeviewer/translation/components/TranslatedJMenuItem.java @@ -0,0 +1,27 @@ +package the.bytecode.club.bytecodeviewer.translation.components; + +import the.bytecode.club.bytecodeviewer.translation.TranslatedComponent; +import the.bytecode.club.bytecodeviewer.translation.Translation; + +import javax.swing.*; + +/** + * @author Konloch + * @since 6/28/2021 + */ +public class TranslatedJMenuItem extends JMenuItem +{ + private final TranslatedComponent component; + + public TranslatedJMenuItem(String text, Translation translation) + { + super(text); + this.component = translation.getTranslatedComponent(); + this.component.runOnUpdate.add(this::updateText); + } + + public void updateText() + { + setText(component.value); + } +} diff --git a/src/main/java/the/bytecode/club/bytecodeviewer/translation/components/TranslatedJRadioButtonMenuItem.java b/src/main/java/the/bytecode/club/bytecodeviewer/translation/components/TranslatedJRadioButtonMenuItem.java new file mode 100644 index 00000000..b3e25290 --- /dev/null +++ b/src/main/java/the/bytecode/club/bytecodeviewer/translation/components/TranslatedJRadioButtonMenuItem.java @@ -0,0 +1,27 @@ +package the.bytecode.club.bytecodeviewer.translation.components; + +import the.bytecode.club.bytecodeviewer.translation.TranslatedComponent; +import the.bytecode.club.bytecodeviewer.translation.Translation; + +import javax.swing.*; + +/** + * @author Konloch + * @since 6/28/2021 + */ +public class TranslatedJRadioButtonMenuItem extends JRadioButtonMenuItem +{ + private final TranslatedComponent component; + + public TranslatedJRadioButtonMenuItem(String text, Translation translation) + { + super(text); + this.component = translation.getTranslatedComponent(); + this.component.runOnUpdate.add(this::updateText); + } + + public void updateText() + { + setText(component.value); + } +} diff --git a/src/main/java/the/bytecode/club/bytecodeviewer/util/MiscUtils.java b/src/main/java/the/bytecode/club/bytecodeviewer/util/MiscUtils.java index 9a89a312..f3fe63f4 100644 --- a/src/main/java/the/bytecode/club/bytecodeviewer/util/MiscUtils.java +++ b/src/main/java/the/bytecode/club/bytecodeviewer/util/MiscUtils.java @@ -2,8 +2,12 @@ package the.bytecode.club.bytecodeviewer.util; import org.apache.commons.lang3.StringUtils; import org.objectweb.asm.tree.ClassNode; +import the.bytecode.club.bytecodeviewer.BytecodeViewer; +import the.bytecode.club.bytecodeviewer.Configuration; +import the.bytecode.club.bytecodeviewer.translation.Language; import javax.imageio.ImageIO; +import javax.swing.*; import java.awt.*; import java.awt.image.BufferedImage; import java.io.*; @@ -13,7 +17,7 @@ import java.nio.charset.StandardCharsets; import java.util.*; import java.util.List; -import static the.bytecode.club.bytecodeviewer.Constants.gson; +import static the.bytecode.club.bytecodeviewer.BytecodeViewer.gson; /*************************************************************************** * Bytecode Viewer (BCV) - Java & Android Reverse Engineering Suite * @@ -235,6 +239,30 @@ public class MiscUtils return asciiEncoder.canEncode(v); } + public static Language guessLanguage() + { + String userLanguage = System.getProperty("user.language"); + String systemLanguageCode = userLanguage != null ? userLanguage.toLowerCase() : ""; + + return Language.getLanguageCodeLookup().getOrDefault(systemLanguageCode, Language.ENGLISH); + } + + public static void setLanguage(Language language) + { + Configuration.language = language; + + try + { + Language.ENGLISH.loadLanguage(); //load english first incase the translation file is missing anything + language.loadLanguage(); //load translation file and swap text around as needed + SwingUtilities.updateComponentTreeUI(BytecodeViewer.viewer); + } + catch (Exception ex) + { + ex.printStackTrace(); + } + } + public static String getChildFromPath(String path) { if (path != null && path.contains("/")) diff --git a/src/main/resources/translations/english.json b/src/main/resources/translations/english.json new file mode 100644 index 00000000..6f09ae8c --- /dev/null +++ b/src/main/resources/translations/english.json @@ -0,0 +1,65 @@ +{ + "FILE": "File", + "ADD": "Add...", + "NEW_WORKSPACE": "New Workspace", + "RELOAD_RESOURCES": "Reload Resources", + "RUN": "Run", + "COMPILE": "Compile", + "SAVE_AS_RUNNABLE_JAR": "Save As Runnable Jar...", + "SAVE_AS_ZIP": "Save As Zip...", + "SAVE_AS_DEX": "Save As DEX...", + "SAVE_AS_APK": "Save As APK...", + "DECOMPILE_SAVE_OPENED_CLASSES": "Decompile & Save Opened Classes", + "DECOMPILE_SAVE_ALL_CLASSES": "Decompile & Save All Classes", + "RECENT_FILES": "Recent Files", + "ABOUT": "About", + "EXIT": "Exit", + + "VIEW": "View", + "VISUAL_SETTINGS": "Visual Settings", + "LANGUAGE": "Language", + "WINDOW_THEME": "Window Theme", + "TEXT_AREA_THEME": "Text Area Theme", + "FONT_SIZE": "Font Size", + "SHOW_TAB_FILE_IN_TAB_TITLE": "Show File In Tab Title", + "SIMPLIFY_NAME_IN_TAB_TITLE": "Simplify Name In Tab Title", + "SYNCHRONIZED_VIEWING": "Synchronized Viewing", + "SHOW_CLASS_METHODS": "Show Class Methods", + + "PANE_1": "Pane 1", + "PANE_2": "Pane 2", + "PANE_3": "Pane 3", + "NONE": "None", + "EDITABLE": "Editable", + + "SETTINGS": "Settings", + "COMPILE_ON_SAVE": "Compile On Save", + "COMPILE_ON_REFRESH": "Compile On Refresh", + "REFRESH_ON_VIEW_CHANGE": "Refresh On View Change", + "DECODE_APK_RESOURCES": "Decode APK Resources", + "APK_CONVERSION": "APK Conversion", + "UPDATE_CHECK": "Update Check", + "DELETE_UNKNOWN_LIBS": "Delete Foreign/Outdated Libs", + "FORCE_PURE_ASCII_AS_TEXT": "Force Pure Ascii As Text", + "SET_PYTHON_27_EXECUTABLE": "Set Python 2.7 Executable", + "SET_PYTHON_30_EXECUTABLE": "Set Python 3.X Executable", + "SET_JRE_RT_LIBRARY": "Set JRE RT Library", + "SET_OPTIONAL_LIBRARY_FOLDER": "Set Optional Library Folder", + "SET_JAVAC_EXECUTABLE": "Set Javac Executable", + "BYTECODE_DECOMPILER": "Bytecode Decompiler", + "DEBUG_HELPERS": "Debug Helpers", + "APPEND_BRACKETS_TO_LABEL": "Append Brackets To Label", + + "PLUGINS": "Plugins", + "OPEN_PLUGIN": "Open Plugin...", + "RECENT_PLUGINS": "Recent Plugins", + "CODE_SEQUENCE_DIAGRAM": "Code Sequence Diagram", + "MALICIOUS_CODE_SCANNER": "Malicious Code Scanner", + "SHOW_MAIN_METHODS": "Show Main Methods", + "SHOW_ALL_STRINGS": "Show All Strings", + "REPLACE_STRINGS": "Replace Strings", + "STACK_FRAMES_REMOVER": "Stack Frames Remover", + "ZKM_STRING_DECRYPTER": "ZKM String Decrypter", + "ALLATORI_STRING_DECRYPTER": "Allatori String Decrypter", + "ZSTRINGARRAY_DECRYPTER": "ZStringArray Decrypter" +} \ No newline at end of file