Commit 4d3f2740 authored by Donlon's avatar Donlon Committed by skylot

Language switch supported

parent f9e7a29c
......@@ -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);
}
}
......@@ -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();
......
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();
}
}
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);
}
}
}
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
......
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=重复上一次操作
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment