Language switch supported

This commit is contained in:
Donlon
2018-08-19 13:27:12 +08:00
committed by skylot
parent f9e7a29c08
commit 4d3f2740ce
12 changed files with 259 additions and 145 deletions
@@ -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);
@@ -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<String> 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();
@@ -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 <T> void populate(GsonBuilder builder, String json, Class<T> type, final T into) {
builder.registerTypeAdapter(type, new InstanceCreator<T>() {
@Override
public T createInstance(Type t) {
return into;
}
}).create().fromJson(json, type);
builder.registerTypeAdapter(type, (InstanceCreator<T>) t -> into)
.create()
.fromJson(json, type);
}
}
@@ -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<EditorTheme> themesCbx = new JComboBox<>(editorThemes);
JComboBox<EditorTheme> 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<LangLocale> 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);
@@ -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);
@@ -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<JNode> 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);
}
}
@@ -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);
@@ -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();
@@ -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();
}
}
+76 -5
View File
@@ -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<LangLocale> i18nLocales = new Vector<>();
private static ResourceBundle messages;
private static Map<LangLocale, Map<String, String>> i18nMessagesMap;
// Use these two fields to avoid invoking Map.get() method twice.
private static Map<String, String> localizedMessagesMap;
private static Map<String, String> 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<String, String> 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<LangLocale> 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);
}
}
}