From 4d3f2740ce6e35e2e6f7ded6d7f78e041b38128a Mon Sep 17 00:00:00 2001 From: Donlon Date: Sun, 19 Aug 2018 13:27:12 +0800 Subject: [PATCH] Language switch supported --- jadx-gui/src/main/java/jadx/gui/JadxGUI.java | 7 + .../java/jadx/gui/settings/JadxSettings.java | 17 +- .../gui/settings/JadxSettingsAdapter.java | 11 +- .../jadx/gui/settings/JadxSettingsWindow.java | 204 ++++++++---------- .../main/java/jadx/gui/ui/AboutDialog.java | 2 +- .../java/jadx/gui/ui/CommonSearchDialog.java | 7 +- .../src/main/java/jadx/gui/ui/LogViewer.java | 4 +- .../src/main/java/jadx/gui/ui/MainWindow.java | 4 +- .../main/java/jadx/gui/utils/LangLocale.java | 34 +++ .../src/main/java/jadx/gui/utils/NLS.java | 81 ++++++- .../resources/i18n/Messages_en_US.properties | 29 ++- .../resources/i18n/Messages_zh_CN.properties | 4 + 12 files changed, 259 insertions(+), 145 deletions(-) create mode 100644 jadx-gui/src/main/java/jadx/gui/utils/LangLocale.java diff --git a/jadx-gui/src/main/java/jadx/gui/JadxGUI.java b/jadx-gui/src/main/java/jadx/gui/JadxGUI.java index d77e6416..c1617e77 100644 --- a/jadx-gui/src/main/java/jadx/gui/JadxGUI.java +++ b/jadx-gui/src/main/java/jadx/gui/JadxGUI.java @@ -2,6 +2,8 @@ package jadx.gui; import javax.swing.*; +import jadx.gui.utils.LangLocale; +import jadx.gui.utils.NLS; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -10,6 +12,10 @@ import jadx.gui.settings.JadxSettingsAdapter; import jadx.gui.ui.MainWindow; import jadx.gui.utils.logs.LogCollector; +import java.util.Locale; +import java.util.prefs.BackingStoreException; +import java.util.prefs.Preferences; + public class JadxGUI { private static final Logger LOG = LoggerFactory.getLogger(JadxGUI.class); @@ -24,6 +30,7 @@ public class JadxGUI { if (!tryDefaultLookAndFeel()) { UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName()); } + NLS.setLocale(settings.getLangLocale()); SwingUtilities.invokeLater(new MainWindow(settings)::open); } catch (Exception e) { LOG.error("Error: {}", e.getMessage(), e); diff --git a/jadx-gui/src/main/java/jadx/gui/settings/JadxSettings.java b/jadx-gui/src/main/java/jadx/gui/settings/JadxSettings.java index cfebfe36..ecc37525 100644 --- a/jadx-gui/src/main/java/jadx/gui/settings/JadxSettings.java +++ b/jadx-gui/src/main/java/jadx/gui/settings/JadxSettings.java @@ -9,6 +9,8 @@ import java.util.List; import java.util.Map; import java.util.Set; +import jadx.gui.utils.LangLocale; +import jadx.gui.utils.NLS; import org.fife.ui.rsyntaxtextarea.RSyntaxTextArea; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -24,7 +26,7 @@ public class JadxSettings extends JadxCLIArgs { private static final String USER_HOME = System.getProperty("user.home"); private static final int RECENT_FILES_COUNT = 15; - private static final int CURRENT_SETTINGS_VERSION = 3; + private static final int CURRENT_SETTINGS_VERSION = 4; private static final Font DEFAULT_FONT = FONT_HACK != null ? FONT_HACK : new RSyntaxTextArea().getFont(); @@ -38,6 +40,7 @@ public class JadxSettings extends JadxCLIArgs { private List recentFiles = new ArrayList<>(); private String fontStr = ""; private String editorThemePath = ""; + private LangLocale langLocale = NLS.defaultLocale(); private boolean autoStartJobs = false; private int settingsVersion = 0; @@ -149,6 +152,14 @@ public class JadxSettings extends JadxCLIArgs { this.showInconsistentCode = showInconsistentCode; } + public LangLocale getLangLocale(){ + return this.langLocale; + } + + public void setLangLocale(LangLocale langLocale) { + this.langLocale = langLocale; + } + public void setCfgOutput(boolean cfgOutput) { this.cfgOutput = cfgOutput; } @@ -253,6 +264,10 @@ public class JadxSettings extends JadxCLIArgs { if (getDeobfuscationMinLength() == 4) { setDeobfuscationMinLength(3); } + fromVersion++; + } + if (fromVersion == 3) { + setLangLocale(NLS.defaultLocale()); } settingsVersion = CURRENT_SETTINGS_VERSION; sync(); diff --git a/jadx-gui/src/main/java/jadx/gui/settings/JadxSettingsAdapter.java b/jadx-gui/src/main/java/jadx/gui/settings/JadxSettingsAdapter.java index 4ffe0689..43f503a0 100644 --- a/jadx-gui/src/main/java/jadx/gui/settings/JadxSettingsAdapter.java +++ b/jadx-gui/src/main/java/jadx/gui/settings/JadxSettingsAdapter.java @@ -48,8 +48,8 @@ public class JadxSettingsAdapter { if (settings == null) { return new JadxSettings(); } - LOG.debug("Loaded settings: {}", makeString(settings)); settings.fixOnLoad(); + LOG.debug("Loaded settings: {}", makeString(settings)); return settings; } catch (Exception e) { LOG.error("Error load settings", e); @@ -81,11 +81,8 @@ public class JadxSettingsAdapter { } private static void populate(GsonBuilder builder, String json, Class type, final T into) { - builder.registerTypeAdapter(type, new InstanceCreator() { - @Override - public T createInstance(Type t) { - return into; - } - }).create().fromJson(json, type); + builder.registerTypeAdapter(type, (InstanceCreator) t -> into) + .create() + .fromJson(json, type); } } diff --git a/jadx-gui/src/main/java/jadx/gui/settings/JadxSettingsWindow.java b/jadx-gui/src/main/java/jadx/gui/settings/JadxSettingsWindow.java index e489f877..c5449c48 100644 --- a/jadx-gui/src/main/java/jadx/gui/settings/JadxSettingsWindow.java +++ b/jadx-gui/src/main/java/jadx/gui/settings/JadxSettingsWindow.java @@ -1,19 +1,14 @@ package jadx.gui.settings; import javax.swing.*; -import javax.swing.event.ChangeEvent; -import javax.swing.event.ChangeListener; import java.awt.*; -import java.awt.event.ActionEvent; -import java.awt.event.ActionListener; import java.awt.event.ItemEvent; -import java.awt.event.ItemListener; import java.awt.event.MouseAdapter; import java.awt.event.MouseEvent; import java.util.Arrays; import java.util.Collection; -import java.util.stream.Stream; +import jadx.gui.utils.LangLocale; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import say.swing.JFontChooser; @@ -34,6 +29,7 @@ public class JadxSettingsWindow extends JDialog { private final transient MainWindow mainWindow; private final transient JadxSettings settings; private final transient String startSettings; + private final transient LangLocale prevLang; private transient boolean needReload = false; @@ -41,16 +37,17 @@ public class JadxSettingsWindow extends JDialog { this.mainWindow = mainWindow; this.settings = settings; this.startSettings = JadxSettingsAdapter.makeString(settings); + this.prevLang = settings.getLangLocale(); initUI(); registerBundledFonts(); setTitle(NLS.str("preferences.title")); setSize(400, 550); - setLocationRelativeTo(null); setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE); setModalityType(ModalityType.APPLICATION_MODAL); pack(); + setLocationRelativeTo(null); } public static void registerBundledFonts() { @@ -70,39 +67,41 @@ public class JadxSettingsWindow extends JDialog { panel.add(makeOtherGroup()); JButton saveBtn = new JButton(NLS.str("preferences.save")); - saveBtn.addActionListener(new ActionListener() { - public void actionPerformed(ActionEvent event) { - settings.sync(); - if (needReload) { - mainWindow.reOpenFile(); - } - dispose(); + saveBtn.addActionListener(event -> { + settings.sync(); + if (needReload) { + mainWindow.reOpenFile(); + } + if (!settings.getLangLocale().equals(prevLang)){ + JOptionPane.showMessageDialog( + this, + NLS.str("msg.language_changed", settings.getLangLocale()), + NLS.str("msg.language_changed_title", settings.getLangLocale()), + JOptionPane.INFORMATION_MESSAGE + ); } + dispose(); }); JButton cancelButton = new JButton(NLS.str("preferences.cancel")); - cancelButton.addActionListener(new ActionListener() { - public void actionPerformed(ActionEvent event) { - JadxSettingsAdapter.fill(settings, startSettings); - dispose(); - } + cancelButton.addActionListener(event -> { + JadxSettingsAdapter.fill(settings, startSettings); + dispose(); }); JButton resetBtn = new JButton(NLS.str("preferences.reset")); - resetBtn.addActionListener(new ActionListener() { - public void actionPerformed(ActionEvent event) { - int res = JOptionPane.showConfirmDialog( - JadxSettingsWindow.this, - NLS.str("preferences.reset_message"), - NLS.str("preferences.reset_title"), - JOptionPane.YES_NO_OPTION); - if (res == JOptionPane.YES_OPTION) { - String defaults = JadxSettingsAdapter.makeString(new JadxSettings()); - JadxSettingsAdapter.fill(settings, defaults); - getContentPane().removeAll(); - initUI(); - pack(); - repaint(); - } + resetBtn.addActionListener(event -> { + int res = JOptionPane.showConfirmDialog( + JadxSettingsWindow.this, + NLS.str("preferences.reset_message"), + NLS.str("preferences.reset_title"), + JOptionPane.YES_NO_OPTION); + if (res == JOptionPane.YES_OPTION) { + String defaults = JadxSettingsAdapter.makeString(new JadxSettings()); + JadxSettingsAdapter.fill(settings, defaults); + getContentPane().removeAll(); + initUI(); + pack(); + repaint(); } }); @@ -124,49 +123,37 @@ public class JadxSettingsWindow extends JDialog { private SettingsGroup makeDeobfuscationGroup() { JCheckBox deobfOn = new JCheckBox(); deobfOn.setSelected(settings.isDeobfuscationOn()); - deobfOn.addItemListener(new ItemListener() { - public void itemStateChanged(ItemEvent e) { - settings.setDeobfuscationOn(e.getStateChange() == ItemEvent.SELECTED); - needReload(); - } + deobfOn.addItemListener(e -> { + settings.setDeobfuscationOn(e.getStateChange() == ItemEvent.SELECTED); + needReload(); }); JCheckBox deobfForce = new JCheckBox(); deobfForce.setSelected(settings.isDeobfuscationForceSave()); - deobfForce.addItemListener(new ItemListener() { - public void itemStateChanged(ItemEvent e) { - settings.setDeobfuscationForceSave(e.getStateChange() == ItemEvent.SELECTED); - needReload(); - } + deobfForce.addItemListener(e -> { + settings.setDeobfuscationForceSave(e.getStateChange() == ItemEvent.SELECTED); + needReload(); }); - final JSpinner minLen = new JSpinner(); + JSpinner minLen = new JSpinner(); minLen.setValue(settings.getDeobfuscationMinLength()); - minLen.addChangeListener(new ChangeListener() { - @Override - public void stateChanged(ChangeEvent e) { - settings.setDeobfuscationMinLength((Integer) minLen.getValue()); - needReload(); - } + minLen.addChangeListener(e -> { + settings.setDeobfuscationMinLength((Integer) minLen.getValue()); + needReload(); }); - final JSpinner maxLen = new JSpinner(); + JSpinner maxLen = new JSpinner(); maxLen.setValue(settings.getDeobfuscationMaxLength()); - maxLen.addChangeListener(new ChangeListener() { - @Override - public void stateChanged(ChangeEvent e) { - settings.setDeobfuscationMaxLength((Integer) maxLen.getValue()); - needReload(); - } + maxLen.addChangeListener(e -> { + settings.setDeobfuscationMaxLength((Integer) maxLen.getValue()); + needReload(); }); JCheckBox deobfSourceAlias = new JCheckBox(); deobfSourceAlias.setSelected(settings.isDeobfuscationUseSourceNameAsAlias()); - deobfSourceAlias.addItemListener(new ItemListener() { - public void itemStateChanged(ItemEvent e) { - settings.setDeobfuscationUseSourceNameAsAlias(e.getStateChange() == ItemEvent.SELECTED); - needReload(); - } + deobfSourceAlias.addItemListener(e -> { + settings.setDeobfuscationUseSourceNameAsAlias(e.getStateChange() == ItemEvent.SELECTED); + needReload(); }); SettingsGroup deobfGroup = new SettingsGroup(NLS.str("preferences.deobfuscation")); @@ -206,7 +193,7 @@ public class JadxSettingsWindow extends JDialog { }); EditorTheme[] editorThemes = CodeArea.getAllThemes(); - final JComboBox themesCbx = new JComboBox<>(editorThemes); + JComboBox themesCbx = new JComboBox<>(editorThemes); for (EditorTheme theme: editorThemes) { if (theme.getPath().equals(settings.getEditorThemePath())) { themesCbx.setSelectedItem(theme); @@ -230,66 +217,49 @@ public class JadxSettingsWindow extends JDialog { private SettingsGroup makeDecompilationGroup() { JCheckBox fallback = new JCheckBox(); fallback.setSelected(settings.isFallbackMode()); - fallback.addItemListener(new ItemListener() { - public void itemStateChanged(ItemEvent e) { - settings.setFallbackMode(e.getStateChange() == ItemEvent.SELECTED); - needReload(); - } + fallback.addItemListener(e -> { + settings.setFallbackMode(e.getStateChange() == ItemEvent.SELECTED); + needReload(); }); JCheckBox showInconsistentCode = new JCheckBox(); showInconsistentCode.setSelected(settings.isShowInconsistentCode()); - showInconsistentCode.addItemListener(new ItemListener() { - public void itemStateChanged(ItemEvent e) { - settings.setShowInconsistentCode(e.getStateChange() == ItemEvent.SELECTED); - needReload(); - } + showInconsistentCode.addItemListener(e -> { + settings.setShowInconsistentCode(e.getStateChange() == ItemEvent.SELECTED); + needReload(); }); JCheckBox resourceDecode = new JCheckBox(); resourceDecode.setSelected(settings.isSkipResources()); - resourceDecode.addItemListener(new ItemListener() { - public void itemStateChanged(ItemEvent e) { - settings.setSkipResources(e.getStateChange() == ItemEvent.SELECTED); - needReload(); - } + resourceDecode.addItemListener(e -> { + settings.setSkipResources(e.getStateChange() == ItemEvent.SELECTED); + needReload(); }); SpinnerNumberModel spinnerModel = new SpinnerNumberModel( settings.getThreadsCount(), 1, Runtime.getRuntime().availableProcessors() * 2, 1); - final JSpinner threadsCount = new JSpinner(spinnerModel); - threadsCount.addChangeListener(new ChangeListener() { - @Override - public void stateChanged(ChangeEvent e) { - settings.setThreadsCount((Integer) threadsCount.getValue()); - needReload(); - } + JSpinner threadsCount = new JSpinner(spinnerModel); + threadsCount.addChangeListener(e -> { + settings.setThreadsCount((Integer) threadsCount.getValue()); + needReload(); }); JCheckBox autoStartJobs = new JCheckBox(); autoStartJobs.setSelected(settings.isAutoStartJobs()); - autoStartJobs.addItemListener(new ItemListener() { - public void itemStateChanged(ItemEvent e) { - settings.setAutoStartJobs(e.getStateChange() == ItemEvent.SELECTED); - } - }); + autoStartJobs.addItemListener(e -> settings.setAutoStartJobs(e.getStateChange() == ItemEvent.SELECTED)); JCheckBox escapeUnicode = new JCheckBox(); escapeUnicode.setSelected(settings.escapeUnicode()); - escapeUnicode.addItemListener(new ItemListener() { - public void itemStateChanged(ItemEvent e) { - settings.setEscapeUnicode(e.getStateChange() == ItemEvent.SELECTED); - needReload(); - } + escapeUnicode.addItemListener(e -> { + settings.setEscapeUnicode(e.getStateChange() == ItemEvent.SELECTED); + needReload(); }); JCheckBox replaceConsts = new JCheckBox(); replaceConsts.setSelected(settings.isReplaceConsts()); - replaceConsts.addItemListener(new ItemListener() { - public void itemStateChanged(ItemEvent e) { - settings.setReplaceConsts(e.getStateChange() == ItemEvent.SELECTED); - needReload(); - } + replaceConsts.addItemListener(e -> { + settings.setReplaceConsts(e.getStateChange() == ItemEvent.SELECTED); + needReload(); }); SettingsGroup other = new SettingsGroup(NLS.str("preferences.decompile")); @@ -304,33 +274,35 @@ public class JadxSettingsWindow extends JDialog { } private SettingsGroup makeOtherGroup() { + JComboBox languageCbx = new JComboBox<>(NLS.getI18nLocales()); + for (LangLocale locale: NLS.getI18nLocales()) { + if (locale.equals(settings.getLangLocale())) { + languageCbx.setSelectedItem(locale); + break; + } + } + languageCbx.addActionListener(e -> settings.setLangLocale((LangLocale) languageCbx.getSelectedItem())); + JCheckBox update = new JCheckBox(); update.setSelected(settings.isCheckForUpdates()); - update.addItemListener(new ItemListener() { - public void itemStateChanged(ItemEvent e) { - settings.setCheckForUpdates(e.getStateChange() == ItemEvent.SELECTED); - } - }); + update.addItemListener(e -> settings.setCheckForUpdates(e.getStateChange() == ItemEvent.SELECTED)); JCheckBox cfg = new JCheckBox(); cfg.setSelected(settings.isCfgOutput()); - cfg.addItemListener(new ItemListener() { - public void itemStateChanged(ItemEvent e) { - settings.setCfgOutput(e.getStateChange() == ItemEvent.SELECTED); - needReload(); - } + cfg.addItemListener(e -> { + settings.setCfgOutput(e.getStateChange() == ItemEvent.SELECTED); + needReload(); }); JCheckBox rawCfg = new JCheckBox(); rawCfg.setSelected(settings.isRawCfgOutput()); - rawCfg.addItemListener(new ItemListener() { - public void itemStateChanged(ItemEvent e) { - settings.setRawCfgOutput(e.getStateChange() == ItemEvent.SELECTED); - needReload(); - } + rawCfg.addItemListener(e -> { + settings.setRawCfgOutput(e.getStateChange() == ItemEvent.SELECTED); + needReload(); }); SettingsGroup other = new SettingsGroup(NLS.str("preferences.other")); + other.addRow(NLS.str("preferences.language"), languageCbx); other.addRow(NLS.str("preferences.check_for_updates"), update); other.addRow(NLS.str("preferences.cfg"), cfg); other.addRow(NLS.str("preferences.raw_cfg"), rawCfg); diff --git a/jadx-gui/src/main/java/jadx/gui/ui/AboutDialog.java b/jadx-gui/src/main/java/jadx/gui/ui/AboutDialog.java index c40c1e66..33c9252d 100644 --- a/jadx-gui/src/main/java/jadx/gui/ui/AboutDialog.java +++ b/jadx-gui/src/main/java/jadx/gui/ui/AboutDialog.java @@ -55,7 +55,7 @@ class AboutDialog extends JDialog { setModalityType(ModalityType.APPLICATION_MODAL); - setTitle("About JADX"); + setTitle(NLS.str("about_dialog.title")); pack(); setDefaultCloseOperation(DISPOSE_ON_CLOSE); setLocationRelativeTo(null); diff --git a/jadx-gui/src/main/java/jadx/gui/ui/CommonSearchDialog.java b/jadx-gui/src/main/java/jadx/gui/ui/CommonSearchDialog.java index 71ce8757..ead77579 100644 --- a/jadx-gui/src/main/java/jadx/gui/ui/CommonSearchDialog.java +++ b/jadx-gui/src/main/java/jadx/gui/ui/CommonSearchDialog.java @@ -301,7 +301,10 @@ public abstract class CommonSearchDialog extends JDialog { protected static class ResultsModel extends AbstractTableModel { private static final long serialVersionUID = -7821286846923903208L; - private static final String[] COLUMN_NAMES = {"Node", "Code"}; + private static final String[] COLUMN_NAMES = { + NLS.str("search_dialog.col_node"), + NLS.str("search_dialog.col_code") + }; private final transient ArrayList rows = new ArrayList<>(); private final transient ResultsTableCellRenderer renderer; @@ -525,7 +528,7 @@ public abstract class CommonSearchDialog extends JDialog { TextSearchIndex textIndex = cache.getTextIndex(); if (textIndex == null) { - warnLabel.setText("Index not initialized, search will be disabled!"); + warnLabel.setText(NLS.str("msg.index_not_initialized")); warnLabel.setVisible(true); } } diff --git a/jadx-gui/src/main/java/jadx/gui/ui/LogViewer.java b/jadx-gui/src/main/java/jadx/gui/ui/LogViewer.java index ee6d3fb4..095a2785 100644 --- a/jadx-gui/src/main/java/jadx/gui/ui/LogViewer.java +++ b/jadx-gui/src/main/java/jadx/gui/ui/LogViewer.java @@ -40,7 +40,7 @@ class LogViewer extends JDialog { level = LEVEL_ITEMS[i]; registerLogListener(); }); - JLabel levelLabel = new JLabel(NLS.str("log.level")); + JLabel levelLabel = new JLabel(NLS.str("log_viewer.log_level")); levelLabel.setLabelFor(cb); controlPane.add(levelLabel); controlPane.add(cb); @@ -56,7 +56,7 @@ class LogViewer extends JDialog { contentPane.add(scrollPane, BorderLayout.CENTER); contentPane.add(close, BorderLayout.PAGE_END); - setTitle("Log Viewer"); + setTitle(NLS.str("log_viewer.title")); pack(); setSize(800, 600); setDefaultCloseOperation(DISPOSE_ON_CLOSE); diff --git a/jadx-gui/src/main/java/jadx/gui/ui/MainWindow.java b/jadx-gui/src/main/java/jadx/gui/ui/MainWindow.java index 2a761daa..3ea8e7be 100644 --- a/jadx-gui/src/main/java/jadx/gui/ui/MainWindow.java +++ b/jadx-gui/src/main/java/jadx/gui/ui/MainWindow.java @@ -154,7 +154,7 @@ public class MainWindow extends JFrame { String[] exts = {"apk", "dex", "jar", "class", "zip", "aar", "arsc"}; String description = "supported files: " + Arrays.toString(exts).replace('[', '(').replace(']', ')'); fileChooser.setFileFilter(new FileNameExtensionFilter(description, exts)); - fileChooser.setToolTipText(NLS.str("file.open")); + fileChooser.setToolTipText(NLS.str("file.open_action")); String currentDirectory = settings.getLastOpenFilePath(); if (!currentDirectory.isEmpty()) { fileChooser.setCurrentDirectory(new File(currentDirectory)); @@ -409,7 +409,7 @@ public class MainWindow extends JFrame { clsSearchAction.putValue(Action.SHORT_DESCRIPTION, NLS.str("menu.class_search")); clsSearchAction.putValue(Action.ACCELERATOR_KEY, getKeyStroke(KeyEvent.VK_N, KeyEvent.CTRL_DOWN_MASK)); - Action deobfAction = new AbstractAction(NLS.str("preferences.deobfuscation"), ICON_DEOBF) { + Action deobfAction = new AbstractAction(NLS.str("menu.deobfuscation"), ICON_DEOBF) { @Override public void actionPerformed(ActionEvent e) { toggleDeobfuscation(); diff --git a/jadx-gui/src/main/java/jadx/gui/utils/LangLocale.java b/jadx-gui/src/main/java/jadx/gui/utils/LangLocale.java new file mode 100644 index 00000000..20aab86c --- /dev/null +++ b/jadx-gui/src/main/java/jadx/gui/utils/LangLocale.java @@ -0,0 +1,34 @@ +package jadx.gui.utils; + +import java.util.Locale; + +public class LangLocale { + private Locale locale; + + public LangLocale(Locale locale) { + this.locale = locale; + } + + public LangLocale(String l, String c) { + this.locale = new Locale(l, c); + } + + public Locale get() { + return locale; + } + + @Override + public String toString() { + return NLS.str("language.name", this); + } + + @Override + public boolean equals(Object obj) { + return obj instanceof LangLocale && locale.equals(((LangLocale) obj).get()); + } + + @Override + public int hashCode() { + return locale.hashCode(); + } +} diff --git a/jadx-gui/src/main/java/jadx/gui/utils/NLS.java b/jadx-gui/src/main/java/jadx/gui/utils/NLS.java index bac5e16d..4df7996b 100644 --- a/jadx-gui/src/main/java/jadx/gui/utils/NLS.java +++ b/jadx-gui/src/main/java/jadx/gui/utils/NLS.java @@ -1,24 +1,95 @@ package jadx.gui.utils; +import java.nio.charset.Charset; +import java.util.HashMap; import java.util.Locale; +import java.util.Map; import java.util.ResourceBundle; +import java.util.Vector; public class NLS { + private static Vector i18nLocales = new Vector<>(); - private static ResourceBundle messages; + private static Map> i18nMessagesMap; + + // Use these two fields to avoid invoking Map.get() method twice. + private static Map localizedMessagesMap; + private static Map fallbackMessagesMap; + + private static LangLocale currentLocale; + private static LangLocale localLocale; + + private static Charset javaCharset; + private static Charset utf8Charset; static { - load(new Locale("en", "US")); + javaCharset = Charset.forName("ISO-8859-1"); + utf8Charset = Charset.forName("UTF-8"); + i18nMessagesMap = new HashMap<>(); + + localLocale = new LangLocale(Locale.getDefault()); + + i18nLocales.add(new LangLocale("en", "US")); // As default language + i18nLocales.add(new LangLocale("zh", "CN")); + + i18nLocales.forEach(NLS::load); + + fallbackMessagesMap = i18nMessagesMap.get(i18nLocales.get(0)); + localizedMessagesMap = i18nMessagesMap.get(i18nLocales.get(0)); } private NLS() { } - private static void load(Locale locale) { - messages = ResourceBundle.getBundle("i18n/Messages", locale); + private static void load(LangLocale locale) { + ResourceBundle bundle = ResourceBundle.getBundle("i18n/Messages", locale.get()); + Map resMap = new HashMap<>(); + + for(String key : bundle.keySet()){ + resMap.put(key, new String( + bundle.getString(key).getBytes(javaCharset), + utf8Charset)); + } + i18nMessagesMap.put(locale, resMap); } public static String str(String key) { - return messages.getString(key); + if(localizedMessagesMap.containsKey(key)){ + return localizedMessagesMap.get(key); + } + return fallbackMessagesMap.get(key);// definitely exists + } + + public static String str(String key, LangLocale locale) { + if(i18nMessagesMap.get(locale).containsKey(key)){ + return i18nMessagesMap.get(locale).get(key); + } + return fallbackMessagesMap.get(key);// definitely exists + } + + public static void setLocale(LangLocale locale) { + if(i18nMessagesMap.containsKey(locale)){ + currentLocale = locale; + } else { + currentLocale = i18nLocales.get(0); + } + localizedMessagesMap = i18nMessagesMap.get(currentLocale); + } + + public static Vector getI18nLocales(){ + return i18nLocales; + } + + public static LangLocale currentLocale() { + return currentLocale; + } + + public static LangLocale defaultLocale(){ + if(i18nMessagesMap.containsKey(localLocale)){ + return localLocale; + } else { + // fallback to english if unsupported + return i18nLocales.get(0); + } } } diff --git a/jadx-gui/src/main/resources/i18n/Messages_en_US.properties b/jadx-gui/src/main/resources/i18n/Messages_en_US.properties index 93aeb75d..3e5965ad 100644 --- a/jadx-gui/src/main/resources/i18n/Messages_en_US.properties +++ b/jadx-gui/src/main/resources/i18n/Messages_en_US.properties @@ -1,3 +1,5 @@ +language.name=English + menu.file=File menu.view=View menu.recent_files=Recent Files @@ -9,14 +11,14 @@ menu.navigation=Navigation menu.text_search=Text search menu.class_search=Class search menu.tools=Tools +menu.deobfuscation=Deobfuscation menu.log=Log Viewer menu.help=Help menu.about=About menu.update_label=New version %s available! -file.open=Open file +file.open_action=Open file... file.open_title=Open file -file.open_action=Open file file.save_all=Save all file.export_gradle=Save as gradle project file.save_all_msg=Select directory for save decompiled sources @@ -45,26 +47,34 @@ nav.forward=Forward search_dialog.open=Open search_dialog.cancel=Cancel -search_dialog.open_by_name=Search for text\: -search_dialog.search_in=Search definitions of \: +search_dialog.open_by_name=Search for text: +search_dialog.search_in=Search definitions of: search_dialog.class=Class search_dialog.method=Method search_dialog.field=Field search_dialog.code=Code -search_dialog.options=Search options \: +search_dialog.options=Search options: search_dialog.ignorecase=Case insensitive search_dialog.next_page=Show next page search_dialog.prev_page=Show previous page -search_dialog.info_label=Showing results %d to %d of %d +search_dialog.info_label=Showing results %1$d to %2$d of %3$d +search_dialog.col_node=Node +search_dialog.col_code=Code usage_dialog.title=Usage search usage_dialog.label=Usage for: +log_viewer.title=Log Viewer +log_viewer.log_level=Log level: + +about_dialog.title=About JADX + preferences.title=Preferences preferences.deobfuscation=Deobfuscation preferences.editor=Editor preferences.decompile=Decompilation preferences.other=Other +preferences.language=Language preferences.check_for_updates=Check for updates on startup preferences.fallback=Fallback mode (simple dump) preferences.showInconsistentCode=Show inconsistent code @@ -90,9 +100,10 @@ preferences.reset_message=Reset settings to default values? preferences.reset_title=Reset settings msg.open_file=Please open file -msg.saving_sources=Saving sources - -log.level=Log level: +msg.saving_sources=Saving sources... +msg.language_changed_title=Language changed +msg.language_changed=New language will be displayed the next time application starts. +msg.index_not_initialized=Index not initialized, search will be disabled! popup.undo=Undo popup.redo=Redo diff --git a/jadx-gui/src/main/resources/i18n/Messages_zh_CN.properties b/jadx-gui/src/main/resources/i18n/Messages_zh_CN.properties index ddb7376e..7607f37d 100644 --- a/jadx-gui/src/main/resources/i18n/Messages_zh_CN.properties +++ b/jadx-gui/src/main/resources/i18n/Messages_zh_CN.properties @@ -1,3 +1,5 @@ +language.name=中文(简体) + menu.file=文件 menu.view=视图 menu.recent_files=最近打开文件 @@ -82,6 +84,8 @@ preferences.reset_title=重置设置 msg.open_file=请打开文件 msg.saving_sources=保存资源 log.level=日志等级: +msg.language_changed_title=语言已更改 +msg.language_changed=在下次启动时将会显示新的语言。 popup.undo=撤销 popup.redo=重复上一次操作 -- GitLab