feat(gui): add export dialog with options (PR #2378)

fix(gui):add export dialog with options (#1983)
This commit is contained in:
Yaroslav
2024-12-21 22:34:54 +02:00
committed by GitHub
parent 73966fda89
commit a46e35c15c
15 changed files with 372 additions and 36 deletions
+1 -1
View File
@@ -12,8 +12,8 @@ import jadx.core.utils.files.FileUtils;
import jadx.gui.logs.LogCollector;
import jadx.gui.settings.JadxSettings;
import jadx.gui.settings.JadxSettingsAdapter;
import jadx.gui.ui.ExceptionDialog;
import jadx.gui.ui.MainWindow;
import jadx.gui.ui.dialog.ExceptionDialog;
import jadx.gui.utils.LafManager;
import jadx.gui.utils.NLS;
import jadx.gui.utils.SystemInfo;
@@ -0,0 +1,53 @@
package jadx.gui.settings;
public class ExportProjectProperties {
private boolean skipSources;
private boolean skipResources;
private boolean asGradleMode;
private boolean useGradleKts;
private String exportPath;
public ExportProjectProperties() {
}
public boolean isSkipSources() {
return skipSources;
}
public void setSkipSources(boolean skipSources) {
this.skipSources = skipSources;
}
public boolean isSkipResources() {
return skipResources;
}
public void setSkipResources(boolean skipResources) {
this.skipResources = skipResources;
}
public boolean isAsGradleMode() {
return asGradleMode;
}
public void setAsGradleMode(boolean asGradleMode) {
this.asGradleMode = asGradleMode;
}
public boolean isUseGradleKts() {
return useGradleKts;
}
public void setUseGradleKts(boolean useGradleKts) {
this.useGradleKts = useGradleKts;
}
public String getExportPath() {
return exportPath;
}
public void setExportPath(String exportPath) {
this.exportPath = exportPath;
}
}
@@ -20,6 +20,7 @@ import java.awt.event.MouseEvent;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.awt.geom.AffineTransform;
import java.io.File;
import java.io.InputStream;
import java.net.URL;
import java.nio.file.Files;
@@ -107,6 +108,7 @@ import jadx.gui.logs.LogOptions;
import jadx.gui.logs.LogPanel;
import jadx.gui.plugins.mappings.RenameMappingsGui;
import jadx.gui.plugins.quark.QuarkDialog;
import jadx.gui.settings.ExportProjectProperties;
import jadx.gui.settings.JadxProject;
import jadx.gui.settings.JadxSettings;
import jadx.gui.settings.ui.JadxSettingsWindow;
@@ -126,6 +128,8 @@ import jadx.gui.ui.codearea.EditorTheme;
import jadx.gui.ui.codearea.EditorViewState;
import jadx.gui.ui.dialog.ADBDialog;
import jadx.gui.ui.dialog.AboutDialog;
import jadx.gui.ui.dialog.ExceptionDialog;
import jadx.gui.ui.dialog.ExportProjectDialog;
import jadx.gui.ui.dialog.LogViewerDialog;
import jadx.gui.ui.dialog.SearchDialog;
import jadx.gui.ui.filedialog.FileDialogWrapper;
@@ -160,7 +164,7 @@ import jadx.gui.utils.shortcut.ShortcutsController;
import jadx.gui.utils.ui.ActionHandler;
import jadx.gui.utils.ui.NodeLabel;
public class MainWindow extends JFrame {
public class MainWindow extends JFrame implements ExportProjectDialog.ExportProjectDialogListener {
private static final Logger LOG = LoggerFactory.getLogger(MainWindow.class);
private static final String DEFAULT_TITLE = "jadx-gui";
@@ -798,23 +802,23 @@ public class MainWindow extends JFrame {
backgroundExecutor.cancelAll();
}
private void saveAll(boolean export) {
FileDialogWrapper fileDialog = new FileDialogWrapper(this, FileOpenMode.EXPORT);
List<Path> saveDirs = fileDialog.show();
if (saveDirs.isEmpty()) {
return;
}
private void exportProject() {
ExportProjectDialog dialog = new ExportProjectDialog(this, this);
dialog.setVisible(true);
}
private void saveAll(ExportProjectProperties exportProjectProperties) {
JadxArgs decompilerArgs = wrapper.getArgs();
decompilerArgs.setExportAsGradleProject(export);
if (export) {
decompilerArgs.setExportAsGradleProject(exportProjectProperties.isAsGradleMode());
if (exportProjectProperties.isAsGradleMode()) {
decompilerArgs.setSkipSources(false);
decompilerArgs.setSkipResources(false);
} else {
decompilerArgs.setSkipSources(settings.isSkipSources());
decompilerArgs.setSkipResources(settings.isSkipResources());
decompilerArgs.setSkipSources(exportProjectProperties.isSkipSources());
decompilerArgs.setSkipResources(exportProjectProperties.isSkipResources());
}
settings.setLastSaveFilePath(fileDialog.getCurrentDir());
backgroundExecutor.execute(new ExportTask(this, wrapper, saveDirs.get(0).toFile()));
backgroundExecutor.execute(new ExportTask(this, wrapper, new File(exportProjectProperties.getExportPath())));
}
public void initTree() {
@@ -1069,8 +1073,7 @@ public class MainWindow extends JFrame {
liveReloadMenuItem = new JCheckBoxMenuItem(liveReloadAction);
liveReloadMenuItem.setState(project.isEnableLiveReload());
JadxGuiAction saveAllAction = new JadxGuiAction(ActionModel.SAVE_ALL, () -> saveAll(false));
JadxGuiAction exportAction = new JadxGuiAction(ActionModel.EXPORT, () -> saveAll(true));
JadxGuiAction exportAction = new JadxGuiAction(ActionModel.EXPORT, this::exportProject);
JMenu recentProjects = new JadxMenu(NLS.str("menu.recent_projects"), shortcutsController);
recentProjects.addMenuListener(new RecentProjectsMenuListener(this, recentProjects));
@@ -1161,7 +1164,6 @@ public class MainWindow extends JFrame {
file.add(liveReloadMenuItem);
renameMappings.addMenuActions(file);
file.addSeparator();
file.add(saveAllAction);
file.add(exportAction);
file.addSeparator();
file.add(recentProjects);
@@ -1245,7 +1247,6 @@ public class MainWindow extends JFrame {
toolbar.addSeparator();
toolbar.add(reloadAction);
toolbar.addSeparator();
toolbar.add(saveAllAction);
toolbar.add(exportAction);
toolbar.addSeparator();
toolbar.add(syncAction);
@@ -1292,7 +1293,6 @@ public class MainWindow extends JFrame {
forwardAction.setEnabled(loaded);
forwardVariantAction.setEnabled(loaded);
syncAction.setEnabled(loaded);
saveAllAction.setEnabled(loaded);
exportAction.setEnabled(loaded);
saveProjectAsAction.setEnabled(loaded);
reloadAction.setEnabled(loaded);
@@ -1790,4 +1790,9 @@ public class MainWindow extends JFrame {
public JadxGuiEventsImpl events() {
return events;
}
@Override
public void onProjectExportCalled(ExportProjectProperties exportProjectProperties) {
saveAll(exportProjectProperties);
}
}
@@ -37,7 +37,7 @@ public enum ActionModel {
Shortcut.keyboard(KeyEvent.VK_F5, InputEvent.SHIFT_DOWN_MASK)),
SAVE_ALL(MENU_TOOLBAR, "file.save_all", "file.save_all", "ui/menu-saveall",
Shortcut.keyboard(KeyEvent.VK_E, UiUtils.ctrlButton())),
EXPORT(MENU_TOOLBAR, "file.export_gradle", "file.export_gradle", "ui/export",
EXPORT(MENU_TOOLBAR, "file.export", "file.export", "ui/export",
Shortcut.keyboard(KeyEvent.VK_E, UiUtils.ctrlButton() | KeyEvent.SHIFT_DOWN_MASK)),
PREFS(MENU_TOOLBAR, "menu.preferences", "menu.preferences", "ui/settings",
Shortcut.keyboard(KeyEvent.VK_P, UiUtils.ctrlButton() | KeyEvent.SHIFT_DOWN_MASK)),
@@ -1,4 +1,4 @@
package jadx.gui.ui;
package jadx.gui.ui.dialog;
import java.awt.BorderLayout;
import java.awt.Color;
@@ -18,7 +18,6 @@ import java.nio.charset.StandardCharsets;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import javax.swing.JButton;
import javax.swing.JComponent;
@@ -46,7 +45,7 @@ public class ExceptionDialog extends JDialog {
private static final String FMT_DETAIL_LENGTH = "-13";
public static void registerUncaughtExceptionHandler() {
Thread.setDefaultUncaughtExceptionHandler((thread, ex) -> showExceptionDialog(thread, ex));
Thread.setDefaultUncaughtExceptionHandler(ExceptionDialog::showExceptionDialog);
}
public static void showExceptionDialog(Thread thread, Throwable ex) {
@@ -78,7 +77,7 @@ public class ExceptionDialog extends JDialog {
try {
// TODO: Use ProcessHandle.current().info().commandLine() once min Java is 9+
List<String> args = ManagementFactory.getRuntimeMXBean().getInputArguments();
details.put("Program args", args.stream().collect(Collectors.joining(" ")));
details.put("Program args", String.join(" ", args));
} catch (Throwable t) {
LOG.error("failed to get program arguments", t);
}
@@ -89,7 +88,7 @@ public class ExceptionDialog extends JDialog {
String issueTitle;
try {
issueTitle = URLEncoder.encode(ex.toString(), StandardCharsets.UTF_8.toString());
issueTitle = URLEncoder.encode(ex.toString(), StandardCharsets.UTF_8);
} catch (Exception e) {
LOG.error("URL encoding of title failed", e);
issueTitle = ex.getClass().getSimpleName();
@@ -105,7 +104,7 @@ public class ExceptionDialog extends JDialog {
String issueBody;
try {
issueBody = URLEncoder.encode(body, StandardCharsets.UTF_8.toString());
issueBody = URLEncoder.encode(body, StandardCharsets.UTF_8);
} catch (Exception e) {
LOG.error("URL encoding of body failed", e);
issueBody = "Please copy the displayed text in the Jadx error dialog and paste it here";
@@ -126,7 +125,7 @@ public class ExceptionDialog extends JDialog {
StringBuilder detailsTextBuilder = new StringBuilder();
details.forEach((key, value) -> detailsTextBuilder.append(String.format("%" + FMT_DETAIL_LENGTH + "s: %s\n", key, value)));
messageArea.setText(detailsTextBuilder.toString() + "\n" + stackTrace);
messageArea.setText(detailsTextBuilder + "\n" + stackTrace);
JPanel buttonPanel = new JPanel();
JButton exitButton = new JButton("Terminate Jadx");
@@ -0,0 +1,207 @@
package jadx.gui.ui.dialog;
import java.awt.BorderLayout;
import java.awt.Container;
import java.awt.Dimension;
import java.awt.event.ItemEvent;
import java.io.File;
import java.nio.file.Path;
import java.util.List;
import javax.swing.BorderFactory;
import javax.swing.Box;
import javax.swing.BoxLayout;
import javax.swing.GroupLayout;
import javax.swing.JButton;
import javax.swing.JCheckBox;
import javax.swing.JDialog;
import javax.swing.JLabel;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JTextField;
import javax.swing.WindowConstants;
import org.jetbrains.annotations.NotNull;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import jadx.gui.settings.ExportProjectProperties;
import jadx.gui.ui.MainWindow;
import jadx.gui.ui.filedialog.FileDialogWrapper;
import jadx.gui.ui.filedialog.FileOpenMode;
import jadx.gui.utils.NLS;
import jadx.gui.utils.TextStandardActions;
import jadx.gui.utils.ui.DocumentUpdateListener;
public class ExportProjectDialog extends JDialog {
public interface ExportProjectDialogListener {
void onProjectExportCalled(ExportProjectProperties exportProjectProperties);
}
private static final Logger LOG = LoggerFactory.getLogger(ExportProjectDialog.class);
private final ExportProjectDialogListener exportProjectDialogListener;
private final ExportProjectProperties exportProjectProperties = new ExportProjectProperties();
private final MainWindow mainWindow;
private JTextField pathField;
public ExportProjectDialog(MainWindow mainWindow, ExportProjectDialogListener exportProjectDialogListener) {
super(mainWindow);
this.mainWindow = mainWindow;
this.exportProjectDialogListener = exportProjectDialogListener;
initUI();
}
private void initUI() {
JPanel contentPane = makeContentPane();
JPanel buttonPane = initButtonsPanel();
Container container = getContentPane();
// contentPane.add(topPanel, BorderLayout.NORTH);
container.add(contentPane, BorderLayout.CENTER);
container.add(buttonPane, BorderLayout.PAGE_END);
setTitle(NLS.str("export_dialog.title"));
pack();
setSize(400, 250);
setLocationRelativeTo(mainWindow);
setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE);
setModalityType(ModalityType.MODELESS);
}
private JPanel makeContentPane() {
JPanel mainPanel = new JPanel();
// top layout
JLabel label = new JLabel(NLS.str("export_dialog.save_path"));
JTextField pathField = new JTextField();
pathField.setText(mainWindow.getSettings().getLastSaveFilePath().toString());
pathField.getDocument().addDocumentListener(new DocumentUpdateListener(ev -> setExportProjectPath(pathField)));
new TextStandardActions(pathField);
JButton browseButton = makeEditorBrowseButton(pathField);
// check box layout
JPanel exportOptionsPanel = new JPanel();
exportOptionsPanel.setBorder(BorderFactory.createTitledBorder(NLS.str("export_dialog.export_options")));
exportOptionsPanel.setLayout(new BoxLayout(exportOptionsPanel, BoxLayout.PAGE_AXIS));
JCheckBox resourceDecode = new JCheckBox(NLS.str("preferences.skipResourcesDecode"));
resourceDecode.setSelected(mainWindow.getSettings().isSkipResources());
resourceDecode.addItemListener(e -> {
exportProjectProperties.setSkipResources(e.getStateChange() == ItemEvent.SELECTED);
});
JCheckBox skipSources = new JCheckBox(NLS.str("preferences.skipSourcesDecode"));
skipSources.setSelected(mainWindow.getSettings().isSkipSources());
skipSources.addItemListener(e -> {
exportProjectProperties.setSkipSources(e.getStateChange() == ItemEvent.SELECTED);
});
JCheckBox exportAsGradleProject = new JCheckBox(NLS.str("export_dialog.export_gradle"));
exportAsGradleProject.addItemListener(e -> {
boolean isSelected = e.getStateChange() == ItemEvent.SELECTED;
exportProjectProperties.setAsGradleMode(isSelected);
resourceDecode.setEnabled(!isSelected);
skipSources.setEnabled(!isSelected);
});
exportOptionsPanel.add(resourceDecode);
exportOptionsPanel.add(skipSources);
exportOptionsPanel.add(exportAsGradleProject);
// build group box layout
JPanel groupBoxPanel = new JPanel();
GroupLayout groupBoxLayout = new GroupLayout(groupBoxPanel);
groupBoxLayout.setAutoCreateGaps(true);
groupBoxLayout.setAutoCreateContainerGaps(true);
groupBoxPanel.setLayout(groupBoxLayout);
groupBoxLayout.setHorizontalGroup(groupBoxLayout.createParallelGroup()
.addComponent(exportOptionsPanel, GroupLayout.DEFAULT_SIZE, GroupLayout.DEFAULT_SIZE, Integer.MAX_VALUE));
groupBoxLayout.setVerticalGroup(groupBoxLayout.createSequentialGroup()
.addComponent(exportOptionsPanel));
// main layout
GroupLayout layout = new GroupLayout(mainPanel);
mainPanel.setLayout(layout);
layout.setAutoCreateGaps(true);
layout.setAutoCreateContainerGaps(true);
// arrange components using GroupLayout
layout.setHorizontalGroup(layout.createParallelGroup(GroupLayout.Alignment.LEADING)
.addGroup(layout.createSequentialGroup()
.addComponent(label)
.addComponent(pathField)
.addComponent(browseButton))
.addComponent(groupBoxPanel));
layout.setVerticalGroup(layout.createSequentialGroup()
.addGroup(layout.createParallelGroup(GroupLayout.Alignment.BASELINE)
.addComponent(label)
.addComponent(pathField)
.addComponent(browseButton))
.addComponent(groupBoxPanel));
return mainPanel;
}
private void setExportProjectPath(JTextField field) {
String path = field.getText();
if (!path.isEmpty()) {
exportProjectProperties.setExportPath(field.getText());
}
}
@NotNull
protected JPanel initButtonsPanel() {
JButton cancelButton = new JButton(NLS.str("common_dialog.cancel"));
cancelButton.addActionListener(event -> dispose());
JButton exportProjectButton = new JButton(NLS.str("common_dialog.ok"));
exportProjectButton.addActionListener(event -> exportProject());
getRootPane().setDefaultButton(exportProjectButton);
JPanel buttonPane = new JPanel();
buttonPane.setLayout(new BoxLayout(buttonPane, BoxLayout.LINE_AXIS));
buttonPane.setBorder(BorderFactory.createEmptyBorder(0, 10, 10, 10));
buttonPane.add(Box.createRigidArea(new Dimension(10, 0)));
buttonPane.add(Box.createHorizontalGlue());
buttonPane.add(exportProjectButton);
buttonPane.add(Box.createRigidArea(new Dimension(10, 0)));
buttonPane.add(cancelButton);
return buttonPane;
}
private JButton makeEditorBrowseButton(final JTextField textField) {
JButton button = new JButton(NLS.str("export_dialog.browse"));
button.addActionListener(e -> {
FileDialogWrapper fileDialog = new FileDialogWrapper(mainWindow, FileOpenMode.EXPORT);
mainWindow.getSettings().setLastSaveFilePath(fileDialog.getCurrentDir());
List<Path> saveDirs = fileDialog.show();
if (saveDirs.isEmpty()) {
return;
}
String path = saveDirs.get(0).toString();
textField.setText(path);
});
return button;
}
private void exportProject() {
if (!new File(exportProjectProperties.getExportPath()).exists()) {
JOptionPane.showMessageDialog(this, NLS.str("message.enter_valid_path"),
NLS.str("message.errorTitle"), JOptionPane.WARNING_MESSAGE);
return;
}
exportProjectDialogListener.onProjectExportCalled(exportProjectProperties);
setVisible(false);
}
}
@@ -45,7 +45,7 @@ file.live_reload_desc=Dateien bei Änderungen autom. neuladen
#file.close_mappings=Zuordnungen exportieren als…
file.save_all=Alles speichern
#file.save=Save
file.export_gradle=Als Gradle-Projekt speichern
#file.export=Export project
file.save_all_msg=Verzeichnis für das Speichern dekompilierter Ressourcen auswählen
file.exit=Beenden
#file.export_node=Export file
@@ -105,6 +105,7 @@ message.taskError=Die Aufgabe ist durch Fehler fehlgeschlagen (siehe Protokoll f
message.errorTitle=Fehler
message.load_errors=Laden fehlgeschlagen.\nAnzahl der Fehler: %d\nKlicke auf OK, um die Log-Ansicht zu öffnen.
message.no_classes=Keine Klassen geladen, nichts zu dekompilieren!
#message.enter_valid_path=Enter valid path to save!
message.saveIncomplete=<html>Speichern unvollständig.<br> %s<br> %d Klassen oder Ressourcen wurden nicht gespeichert!</html>
message.indexIncomplete=<html>Index einiger Klassen übersprungen.<br> %s<br> %d Klassen wurden nicht indiziert und werden nicht in den Suchergebnissen erscheinen!</html>
@@ -170,6 +171,12 @@ comment_dialog.usage=Shift + Enter verwenden, um eine neue Zeile zu beginnen
#rename_dialog.class_help=Enter full name to move class to another package. Start with '.' to move to default (empty) package
#export_dialog.title=Export to source code
#export_dialog.save_path=Save path:
#export_dialog.browse=Browse
#export_dialog.export_options=Export options
#export_dialog.export_gradle=Export as gradle project
log_viewer.title=Log-Anzeige
log_viewer.log_level=Log-Level:
#log_viewer.mode=Mode:
@@ -213,6 +220,7 @@ preferences.inlineMethods=Inline-Methoden
#preferences.restoreSwitchOverString=Restore switch over string
preferences.fsCaseSensitive=Dateisystem unterscheidet zwischen Groß/Kleinschreibung
preferences.skipResourcesDecode=Keine Ressourcen dekodieren
#preferences.skipSourcesDecode=Don't decompile source code
preferences.useKotlinMethodsForVarNames=Kotlin-Methoden für die Umbenennung von Variablen verwenden
preferences.commentsLevel=Level der Code Kommentierungen
#preferences.saveOption=Auto-save settings
@@ -45,7 +45,7 @@ file.save_mappings_as=Save mappings as...
file.close_mappings=Close mappings
file.save_all=Save all
file.save=Save
file.export_gradle=Save as gradle project
file.export=Export project
file.save_all_msg=Select directory for save decompiled sources
file.exit=Exit
file.export_node=Export file
@@ -105,6 +105,7 @@ message.taskError=Task failed with error (check log for details).
message.errorTitle=Error
message.load_errors=Load failed.\nErrors count: %d\nClick OK to open log viewer.
message.no_classes=No classes loaded, nothing to decompile!
message.enter_valid_path=Enter valid path to save!
message.saveIncomplete=<html>Save incomplete.<br> %s<br> %d classes or resources were not saved!</html>
message.indexIncomplete=<html>Index of some classes skipped.<br> %s<br> %d classes were not indexed and will not appear in search results!</html>
@@ -170,6 +171,12 @@ comment_dialog.usage=Use 'Shift + Enter' to start a new line
rename_dialog.class_help=Enter full name to move class to another package. Start with '.' to move to default (empty) package
export_dialog.title=Export to source code
export_dialog.save_path=Save path:
export_dialog.browse=Browse
export_dialog.export_options=Export options
export_dialog.export_gradle=Export as gradle project
log_viewer.title=Log Viewer
log_viewer.log_level=Log level:
log_viewer.mode=Mode:
@@ -213,6 +220,7 @@ preferences.extractFinally=Extract finally block
preferences.restoreSwitchOverString=Restore switch over string
preferences.fsCaseSensitive=File system is case-sensitive
preferences.skipResourcesDecode=Don't decode resources
preferences.skipSourcesDecode=Don't decompile source code
preferences.useKotlinMethodsForVarNames=Use kotlin methods for variables rename
preferences.commentsLevel=Code comments level
preferences.saveOption=Auto-save settings
@@ -45,7 +45,7 @@ file.open_title=Abrir archivo
#file.close_mappings=Close mappings
file.save_all=Guardar todo
#file.save=Save
file.export_gradle=Guardar como proyecto Gradle
#file.export=Export project
file.save_all_msg=Seleccionar carpeta para guardar fuentes descompiladas
file.exit=Salir
#file.export_node=Export file
@@ -105,6 +105,7 @@ nav.forward=Adelante
#message.errorTitle=Error
#message.load_errors=Load failed.\nErrors count: %d\nClick OK to open log viewer.
#message.no_classes=No classes loaded, nothing to decompile!
#message.enter_valid_path=Enter valid path to save!
#message.saveIncomplete=<html>Save incomplete.<br> %s<br> %d classes or resources were not saved!</html>
#message.indexIncomplete=<html>Index of some classes skipped.<br> %s<br> %d classes were not indexed and will not appear in search results!</html>
@@ -170,6 +171,12 @@ usage_dialog.label=Usage for:
#rename_dialog.class_help=Enter full name to move class to another package. Start with '.' to move to default (empty) package
#export_dialog.title=Export to source code
#export_dialog.save_path=Save path:
#export_dialog.browse=Browse
#export_dialog.export_options=Export options
#export_dialog.export_gradle=Export as gradle project
log_viewer.title=Visor log
log_viewer.log_level=Nivel log:
#log_viewer.mode=Mode:
@@ -213,6 +220,7 @@ preferences.replaceConsts=Reemplazar constantes
#preferences.restoreSwitchOverString=Restore switch over string
#preferences.fsCaseSensitive=File system is case-sensitive
preferences.skipResourcesDecode=No descodificar recursos
#preferences.skipSourcesDecode=Don't decompile source code
#preferences.useKotlinMethodsForVarNames=Use kotlin methods for variables rename
#preferences.commentsLevel=Code comments level
#preferences.saveOption=Auto-save settings
@@ -45,7 +45,7 @@ file.save_mappings_as=Simpan pemetaan sebagai...
file.close_mappings=Tutup pemetaan
file.save_all=Simpan semua
file.save=Simpan
file.export_gradle=Simpan sebagai proyek gradle
#file.export=Export project
file.save_all_msg=Pilih direktori untuk menyimpan sumber yang telah didekompilasi
file.exit=Keluar
#file.export_node=Export file
@@ -105,6 +105,7 @@ message.taskError=Tugas gagal dengan kesalahan (periksa log untuk detailnya).
message.errorTitle=Kesalahan
message.load_errors=Pemuatan gagal.\nJumlah kesalahan: %d\nKlik OK untuk membuka penampil log.
message.no_classes=Tidak ada kelas yang dimuat, tidak ada yang dapat didekompilasi!
#message.enter_valid_path=Enter valid path to save!
message.saveIncomplete=<html>Simpan tidak lengkap.<br> %s<br> %d kelas atau sumber daya tidak disimpan!</html>
message.indexIncomplete=<html>Indeks beberapa kelas dilewati.<br> %s<br> %d kelas tidak diindeks dan tidak akan muncul dalam hasil pencarian!</html>
@@ -170,6 +171,12 @@ comment_dialog.usage=Gunakan Shift + Enter untuk memulai baris baru
#rename_dialog.class_help=Enter full name to move class to another package. Start with '.' to move to default (empty) package
#export_dialog.title=Export to source code
#export_dialog.save_path=Save path:
#export_dialog.browse=Browse
#export_dialog.export_options=Export options
#export_dialog.export_gradle=Export as gradle project
log_viewer.title=Pemantau Log
log_viewer.log_level=Tingkat log:
log_viewer.mode=Mode:
@@ -213,6 +220,7 @@ preferences.extractFinally=Ekstrak blok finally
#preferences.restoreSwitchOverString=Restore switch over string
preferences.fsCaseSensitive=File sistem bersifat sensitif huruf besar-kecil
preferences.skipResourcesDecode=Jangan dekode sumber daya
#preferences.skipSourcesDecode=Don't decompile source code
preferences.useKotlinMethodsForVarNames=Gunakan metode Kotlin untuk mengganti nama variabel
preferences.commentsLevel=Tingkat komentar kode
#preferences.saveOption=Auto-save settings
@@ -45,7 +45,7 @@ file.save_mappings=다른 이름으로 매핑 내보내기...
#file.close_mappings=다른 이름으로 매핑 내보내기...
file.save_all=모두 저장
#file.save=Save
file.export_gradle=Gradle 프로젝트로 저장
#file.export=Export project
file.save_all_msg=디컴파일된 소스를 저장할 디렉토리 선택
file.exit=나가기
#file.export_node=Export file
@@ -105,6 +105,7 @@ message.taskError=작업이 오류로 실패했습니다. (자세한 내용은
message.errorTitle=오류
message.load_errors=로드하지 못했습니다.\n오류 수: %d\n로그 뷰어를 열려면 확인을 클릭하십시오.
message.no_classes=로드된 클래스 없음, 디컴파일 대상 존재하지 않음
#message.enter_valid_path=Enter valid path to save!
message.saveIncomplete=<html>저장이 완료되지 않았습니다.<br> %s<br> %d개의 클래스 또는 리소스가 저장되지 않았습니다!</html>
message.indexIncomplete=<html>일부 클래스의 색인을 건너뛰었습니다.<br> %s<br> %d개의 클래스는 색인이 생성되지 않았으며 검색 결과에 나타나지 않습니다.</html>
@@ -170,6 +171,12 @@ comment_dialog.usage=Shift + Enter 를 입력해 새 라인에 입력
#rename_dialog.class_help=Enter full name to move class to another package. Start with '.' to move to default (empty) package
#export_dialog.title=Export to source code
#export_dialog.save_path=Save path:
#export_dialog.browse=Browse
#export_dialog.export_options=Export options
#export_dialog.export_gradle=Export as gradle project
log_viewer.title=로그 뷰어
log_viewer.log_level=로그 레벨:
#log_viewer.mode=Mode:
@@ -213,6 +220,7 @@ preferences.extractFinally=finally 블록 추출
#preferences.restoreSwitchOverString=Restore switch over string
preferences.fsCaseSensitive=파일 시스템 대소문자 구별
preferences.skipResourcesDecode=리소스 디코딩 하지 않기
#preferences.skipSourcesDecode=Don't decompile source code
preferences.useKotlinMethodsForVarNames=변수 이름 바꾸기에 kotlin 메서드 사용
preferences.commentsLevel=코드 주석 수준
#preferences.saveOption=Auto-save settings
@@ -45,7 +45,7 @@ file.live_reload_desc=Recarregar arquivos automaticamente ao serem alterados
#file.close_mappings=Close mappings
file.save_all=Salvar tudo
#file.save=Save
file.export_gradle=Salvar como um projeto gradle
#file.export=Export project
file.save_all_msg=Selecionar diretório para salvar arquivos descompilados
file.exit=Sair
#file.export_node=Export file
@@ -105,6 +105,7 @@ message.taskError=Tarefa falhou com erro (cheque o log para detalhes).
message.errorTitle=Erro
message.load_errors=Carregamento falhou.\nNúmero de erros: %d\nClique em ok para abrir o visualizador de log.
message.no_classes=Nenhuma classe carregada, nada para ser descompilado!
#message.enter_valid_path=Enter valid path to save!
message.saveIncomplete=<html>Operação não foi completa.<br> %s<br> %d classes ou recursos não foram salvos!</html>
message.indexIncomplete=<html>Indexação de algumas classes foram ignoradas.<br> %s<br> %d classes não foram indexadas não vão aparecer nos resultados de busca!</html>
@@ -170,6 +171,12 @@ comment_dialog.usage=Use Shift + Enter para pular uma linha
#rename_dialog.class_help=Enter full name to move class to another package. Start with '.' to move to default (empty) package
#export_dialog.title=Export to source code
#export_dialog.save_path=Save path:
#export_dialog.browse=Browse
#export_dialog.export_options=Export options
#export_dialog.export_gradle=Export as gradle project
log_viewer.title=Visualizador de log
log_viewer.log_level=Nível do log:
#log_viewer.mode=Mode:
@@ -213,6 +220,7 @@ preferences.extractFinally=Extrair blocos finally
#preferences.restoreSwitchOverString=Restore switch over string
preferences.fsCaseSensitive=Sistema de arquivo diferencia maiúsculas de minúsculas
preferences.skipResourcesDecode=Não decodificar recursos
#preferences.skipSourcesDecode=Don't decompile source code
preferences.useKotlinMethodsForVarNames=Usar métodos do kotlin para renomear variáveis
preferences.commentsLevel=Nível de comentários do código
#preferences.saveOption=Auto-save settings
@@ -45,7 +45,7 @@ file.save_mappings_as=Сохранить маппинги как...
file.close_mappings=Закрыть маппинги
file.save_all=Сохранить все
file.save=Сохранить
file.export_gradle=Сохранить как Gradle проект
#file.export=Export project
file.save_all_msg=Выбрать папку для декомпилированных проектов
file.exit=Выход
#file.export_node=Export file
@@ -105,6 +105,7 @@ message.taskError=Выполнение задачи закончено с оши
message.errorTitle=Ошибка
message.load_errors=Ошибка прогрузки.\nКоличество ошибок: %d\nНажмите OK, чтобы открыть лог.
message.no_classes=Классы не найдены, нечего декомпилировать!
#message.enter_valid_path=Enter valid path to save!
message.saveIncomplete=<html>Сохранение не завершено.<br> %s<br> %d классов или ресурсов не сохранено!</html>
message.indexIncomplete=<html>Индексирование некоторых классов пропущено.<br> %s<br> %d классов не индексировано, и не будет отображаться в результатах поиска!</html>
@@ -170,6 +171,12 @@ comment_dialog.usage=Используйте Shift + Enter для перенос
rename_dialog.class_help=Введите полный путь к пакету, в который вы хотите переместить этот класс. Введите '.' для перемещения в пакет по умолчанию (пустой) пакет
#export_dialog.title=Export to source code
#export_dialog.save_path=Save path:
#export_dialog.browse=Browse
#export_dialog.export_options=Export options
#export_dialog.export_gradle=Export as gradle project
log_viewer.title=Просмотр логов
log_viewer.log_level=Уровень лога:
log_viewer.mode=Режим:
@@ -213,6 +220,7 @@ preferences.extractFinally=Вычленять finally блоки
#preferences.restoreSwitchOverString=Restore switch over string
preferences.fsCaseSensitive=Учитывать регистр в файловой системе
preferences.skipResourcesDecode=Не декодировать ресурсы
#preferences.skipSourcesDecode=Don't decompile source code
preferences.useKotlinMethodsForVarNames=Kotlin методы как имена полей
preferences.commentsLevel=Уровень лога операций
preferences.saveOption=Автосохранение настроек
@@ -45,7 +45,7 @@ file.save_mappings_as=映射另存为…
file.close_mappings=关闭映射
file.save_all=全部保存(伪代码)
file.save=保存
file.export_gradle=另存为 Gradle 项目
#file.export=Export project
file.save_all_msg=请选择保存反编译资源的目录
file.exit=退出
file.export_node=导出文件
@@ -105,6 +105,7 @@ message.taskError=任务失败,出现错误(详情请查看日志)。
message.errorTitle=错误
message.load_errors=加载失败。\n错误数:%d\n点击确定查看日志
message.no_classes=无类被加载,没什么可反编译!
#message.enter_valid_path=Enter valid path to save!
message.saveIncomplete=<html>保存未完成。<br> %s<br> %d 类或资源未被保存!</html>
message.indexIncomplete=<html>已跳过某些类索引。<br> %s<br> %d 类未被索引,不会出现在搜索结果中!</html>
@@ -170,6 +171,12 @@ comment_dialog.usage=使用 Shift + Enter 换行
rename_dialog.class_help=输入包名全路径,将类移动到另一个包。以'.'开始则移到默认(空)包下。
#export_dialog.title=Export to source code
#export_dialog.save_path=Save path:
#export_dialog.browse=Browse
#export_dialog.export_options=Export options
#export_dialog.export_gradle=Export as gradle project
log_viewer.title=日志查看器
log_viewer.log_level=日志等级:
log_viewer.mode=模式:
@@ -213,6 +220,7 @@ preferences.extractFinally=提取finally块
preferences.restoreSwitchOverString=恢复switch字符串
preferences.fsCaseSensitive=文件系统区分大小写
preferences.skipResourcesDecode=不反编译资源文件
#preferences.skipSourcesDecode=Don't decompile source code
preferences.useKotlinMethodsForVarNames=使用Kotlin方法来重命名变量
preferences.commentsLevel=代码注释等级
preferences.saveOption=自动保存设置
@@ -45,7 +45,7 @@ file.save_mappings_as=儲存對映檔為...
file.close_mappings=關閉對映檔
file.save_all=全部儲存
file.save=儲存
file.export_gradle=另存為 gradle 專案
#file.export=Export project
file.save_all_msg=選擇儲存反編譯原始碼的路徑
file.exit=離開
#file.export_node=Export file
@@ -105,6 +105,7 @@ message.taskError=工作發生錯誤 (查看記錄檔以了解詳情)。
message.errorTitle=錯誤
message.load_errors=載入失敗。\n錯誤數:%d\n點擊 OK 以開啟記錄檔檢視器
message.no_classes=未載入任何類別,沒有東西可以反編譯!
#message.enter_valid_path=Enter valid path to save!
message.saveIncomplete=<html>儲存未完成。<br> %s<br> %d 個類別或資源尚未儲存!</html>
message.indexIncomplete=<html>某些類別的索引被略過。<br> %s<br> %d 個類別未被索引,故不會出現在搜尋結果中。</html>
@@ -170,6 +171,12 @@ comment_dialog.usage=按下 Shift + Enter 來換行
#rename_dialog.class_help=Enter full name to move class to another package. Start with '.' to move to default (empty) package
#export_dialog.title=Export to source code
#export_dialog.save_path=Save path:
#export_dialog.browse=Browse
#export_dialog.export_options=Export options
#export_dialog.export_gradle=Export as gradle project
log_viewer.title=記錄檔檢視器
log_viewer.log_level=記錄層級:
log_viewer.mode=模式:
@@ -213,6 +220,7 @@ preferences.extractFinally=擷取 finally 區塊
#preferences.restoreSwitchOverString=Restore switch over string
preferences.fsCaseSensitive=檔案系統區分大小寫
preferences.skipResourcesDecode=不要為資源解碼
#preferences.skipSourcesDecode=Don't decompile source code
preferences.useKotlinMethodsForVarNames=使用 Kotlin 方法來為變數重新命名
preferences.commentsLevel=程式碼註解層級
#preferences.saveOption=Auto-save settings