diff --git a/.gitignore b/.gitignore index 1c2162e..97806c7 100644 --- a/.gitignore +++ b/.gitignore @@ -27,6 +27,7 @@ xcuserdata/ *.swp *.swo *~ +.run # Temporary files *.tmp diff --git a/Package.swift b/Package.swift index 73e5745..972b0e0 100644 --- a/Package.swift +++ b/Package.swift @@ -3,6 +3,7 @@ import PackageDescription let package = Package( name: "Aether", + defaultLocalization: "en", platforms: [ .macOS(.v14) ], diff --git a/Sources/Aether/App/AetherApp.swift b/Sources/Aether/App/AetherApp.swift index 4703d13..078f4ae 100644 --- a/Sources/Aether/App/AetherApp.swift +++ b/Sources/Aether/App/AetherApp.swift @@ -4,35 +4,38 @@ import SwiftUI struct AetherApp: App { @StateObject private var appState = AppState() @NSApplicationDelegateAdaptor(AppDelegate.self) var appDelegate + @AppStorage("appLanguage") private var appLanguage = AppLanguage.systemCode var body: some Scene { WindowGroup { MainView() + .id(appLanguage) .environmentObject(appState) + .environment(\.locale, AppLanguage.fromStored(appLanguage).locale) .preferredColorScheme(.dark) } .windowStyle(.hiddenTitleBar) .commands { CommandGroup(replacing: .newItem) { - Button("Open Binary...") { + Button(translate("menu.file.openBinary")) { appState.openFile() } .keyboardShortcut("o", modifiers: .command) - Button("Open Project...") { + Button(translate("menu.file.openProject")) { appState.openProject() } .keyboardShortcut("o", modifiers: [.command, .shift]) Divider() - Button("Save Binary As...") { + Button(translate("menu.file.saveBinaryAs")) { appState.saveFileAs() } .keyboardShortcut("s", modifiers: .command) .disabled(appState.currentFile == nil) - Button("Save Project As...") { + Button(translate("menu.file.saveProjectAs")) { appState.saveProjectAs() } .keyboardShortcut("s", modifiers: [.command, .shift]) @@ -40,7 +43,7 @@ struct AetherApp: App { Divider() - Button("Close") { + Button(translate("menu.file.close")) { appState.closeFile() } .keyboardShortcut("w", modifiers: .command) @@ -48,26 +51,26 @@ struct AetherApp: App { } CommandGroup(replacing: .undoRedo) { - Button("Undo") { + Button(translate("menu.edit.undo")) { appState.undo() } .keyboardShortcut("z", modifiers: .command) .disabled(!appState.canUndo) - Button("Redo") { + Button(translate("menu.edit.redo")) { appState.redo() } .keyboardShortcut("z", modifiers: [.command, .shift]) .disabled(!appState.canRedo) } - CommandMenu("Analysis") { - Button("Analyze All") { + CommandMenu(translate("menu.analysis.title")) { + Button(translate("menu.analysis.analyzeAll")) { appState.analyzeAll() } .keyboardShortcut("a", modifiers: [.command, .shift]) .disabled(appState.currentFile == nil) - Button("Find Functions") { + Button(translate("menu.analysis.findFunctions")) { appState.findFunctions() } .keyboardShortcut("f", modifiers: [.command, .shift]) @@ -75,19 +78,19 @@ struct AetherApp: App { Divider() - Button("Show CFG") { + Button(translate("menu.analysis.showCFG")) { appState.showCFG = true } .keyboardShortcut("g", modifiers: .command) .disabled(appState.selectedFunction == nil) - Button("Decompile") { + Button(translate("menu.analysis.decompile")) { appState.decompileCurrentFunction() } .keyboardShortcut("d", modifiers: [.command, .shift]) .disabled(appState.selectedFunction == nil) - Button("Generate Pseudo-Code") { + Button(translate("menu.analysis.generatePseudoCode")) { appState.generateStructuredCode() } .keyboardShortcut("p", modifiers: [.command, .shift]) @@ -95,96 +98,96 @@ struct AetherApp: App { Divider() - Button("Call Graph") { + Button(translate("menu.analysis.callGraph")) { appState.showCallGraph = true } .keyboardShortcut("k", modifiers: .command) .disabled(appState.currentFile == nil) - Button("Crypto Detection") { + Button(translate("menu.analysis.cryptoDetection")) { appState.runCryptoDetection() } .disabled(appState.currentFile == nil) - Button("Deobfuscation Analysis") { + Button(translate("menu.analysis.deobfuscation")) { appState.runDeobfuscation() } .disabled(appState.selectedFunction == nil) - Button("Type Recovery") { + Button(translate("menu.analysis.typeRecovery")) { appState.runTypeRecovery() } .disabled(appState.selectedFunction == nil) - Button("Idiom Recognition") { + Button(translate("menu.analysis.idiomRecognition")) { appState.runIdiomRecognition() } .disabled(appState.selectedFunction == nil) Divider() - Button("Show Jump Table") { + Button(translate("menu.analysis.showJumpTable")) { appState.showJumpTable = true } .keyboardShortcut("j", modifiers: [.command, .shift]) .disabled(appState.selectedFunction == nil) } - CommandMenu("Export") { - Button("Export to IDA Python...") { + CommandMenu(translate("menu.export.title")) { + Button(translate("menu.export.ida")) { appState.showExportSheet = true } .disabled(appState.currentFile == nil) - Button("Export to Ghidra XML...") { + Button(translate("menu.export.ghidra")) { exportWithFormat(.ghidraXML) } .disabled(appState.currentFile == nil) - Button("Export to Radare2...") { + Button(translate("menu.export.radare2")) { exportWithFormat(.radare2) } .disabled(appState.currentFile == nil) - Button("Export to Binary Ninja...") { + Button(translate("menu.export.binaryNinja")) { exportWithFormat(.binaryNinja) } .disabled(appState.currentFile == nil) Divider() - Button("Export to JSON...") { + Button(translate("menu.export.json")) { exportWithFormat(.json) } .disabled(appState.currentFile == nil) - Button("Export to CSV...") { + Button(translate("menu.export.csv")) { exportWithFormat(.csv) } .disabled(appState.currentFile == nil) - Button("Export to HTML Report...") { + Button(translate("menu.export.html")) { exportWithFormat(.html) } .disabled(appState.currentFile == nil) - Button("Export to Markdown...") { + Button(translate("menu.export.markdown")) { exportWithFormat(.markdown) } .disabled(appState.currentFile == nil) - Button("Export C Header...") { + Button(translate("menu.export.cheader")) { exportWithFormat(.cHeader) } .disabled(appState.currentFile == nil) } - CommandMenu("Navigate") { - Button("Go to Address...") { + CommandMenu(translate("menu.navigate.title")) { + Button(translate("menu.navigate.gotoAddress")) { appState.showGoToAddress = true } .keyboardShortcut("g", modifiers: [.command, .shift]) - Button("Search...") { + Button(translate("menu.navigate.search")) { appState.showSearch = true } .keyboardShortcut("f", modifiers: .command) @@ -193,14 +196,16 @@ struct AetherApp: App { Settings { SettingsView() + .id(appLanguage) .environmentObject(appState) + .environment(\.locale, AppLanguage.fromStored(appLanguage).locale) } } private func exportWithFormat(_ format: ExportManager.ExportFormat) { let panel = NSSavePanel() panel.allowedContentTypes = [.data] - panel.nameFieldStringValue = "\(appState.currentFile?.name ?? "export").\(format.fileExtension)" + panel.nameFieldStringValue = "\(appState.currentFile?.name ?? translate("export.defaultFileName")).\(format.fileExtension)" if panel.runModal() == .OK, let url = panel.url { appState.exportTo(format: format, url: url) @@ -215,22 +220,22 @@ struct SettingsView: View { TabView { GeneralSettingsView() .tabItem { - Label("General", systemImage: "gear") + Label(translate("settings.tab.general"), systemImage: "gear") } AppearanceSettingsView() .tabItem { - Label("Appearance", systemImage: "paintbrush") + Label(translate("settings.tab.appearance"), systemImage: "paintbrush") } AnalysisSettingsView() .tabItem { - Label("Analysis", systemImage: "cpu") + Label(translate("settings.tab.analysis"), systemImage: "cpu") } AISettingsTab() .tabItem { - Label("AI", systemImage: "brain") + Label(translate("settings.tab.ai"), systemImage: "brain") } } .frame(width: 500, height: 350) @@ -240,13 +245,24 @@ struct SettingsView: View { struct GeneralSettingsView: View { @AppStorage("autoAnalyze") private var autoAnalyze = true @AppStorage("showHexView") private var showHexView = true + @AppStorage("appLanguage") private var appLanguage = AppLanguage.systemCode var body: some View { Form { - Toggle("Auto-analyze on file open", isOn: $autoAnalyze) - Toggle("Show Hex View by default", isOn: $showHexView) + Picker(translate("settings.general.language"), selection: $appLanguage) { + ForEach(AppLanguage.available) { language in + Text(language.pickerLabel).tag(language.code) + } + } + Toggle(translate("settings.general.autoAnalyze"), isOn: $autoAnalyze) + Toggle(translate("settings.general.showHexView"), isOn: $showHexView) } .padding() + .onAppear { + if !AppLanguage.isValidStorageValue(appLanguage) { + appLanguage = AppLanguage.systemCode + } + } } } @@ -256,7 +272,7 @@ struct AppearanceSettingsView: View { var body: some View { Form { - Picker("Font", selection: $fontName) { + Picker(translate("settings.appearance.font"), selection: $fontName) { Text("SF Mono").tag("SF Mono") Text("Menlo").tag("Menlo") Text("Monaco").tag("Monaco") @@ -264,7 +280,16 @@ struct AppearanceSettingsView: View { } Slider(value: $fontSize, in: 10...20, step: 1) { - Text("Font Size: \(Int(fontSize))") + Text(translate("settings.appearance.fontSize")) + } + + HStack { + Text(translate("settings.appearance.fontSize")) + .font(.caption) + .foregroundColor(.secondary) + Text("\(Int(fontSize))") + .font(.caption) + .foregroundColor(.secondary) } } .padding() @@ -278,9 +303,9 @@ struct AnalysisSettingsView: View { var body: some View { Form { - Toggle("Deep analysis (slower)", isOn: $deepAnalysis) - Toggle("Analyze strings", isOn: $analyzeStrings) - Toggle("Analyze cross-references", isOn: $analyzeXRefs) + Toggle(translate("settings.analysis.deepAnalysis"), isOn: $deepAnalysis) + Toggle(translate("settings.analysis.analyzeStrings"), isOn: $analyzeStrings) + Toggle(translate("settings.analysis.analyzeXrefs"), isOn: $analyzeXRefs) } .padding() } diff --git a/Sources/Aether/App/AppLanguage.swift b/Sources/Aether/App/AppLanguage.swift new file mode 100644 index 0000000..348503f --- /dev/null +++ b/Sources/Aether/App/AppLanguage.swift @@ -0,0 +1,153 @@ +import Foundation + +struct AppLanguage: Identifiable, Hashable { + static let systemCode = "system" + static let system = AppLanguage(code: systemCode) + + let code: String + + var id: String { code } + + var locale: Locale { + isSystem ? .autoupdatingCurrent : Locale(identifier: code) + } + + var isSystem: Bool { + code == Self.systemCode + } + + var displayName: String { + if isSystem { + return translate("settings.language.system") + } + + return Self.localizedName(for: code) + } + + var flagEmoji: String { + if isSystem { + return "🌐" + } + + return Self.flagEmoji(forLanguageCode: code) ?? "🏳️" + } + + var pickerLabel: String { + "\(flagEmoji) \(displayName)" + } + + init(code: String) { + self.code = Self.normalizeLanguageCode(code) + } + + static var available: [AppLanguage] { + [system] + availableLanguageCodes.map { AppLanguage(code: $0) } + } + + static var supportedLanguageCodes: Set { + Set(availableLanguageCodes) + } + + static var fallbackLanguageCode: String? { + if let dev = Bundle.module.developmentLocalization.map(normalizeLanguageCode), + supportedLanguageCodes.contains(dev) { + return dev + } + + return availableLanguageCodes.first + } + + static func fromStored(_ storedValue: String?) -> AppLanguage { + guard let storedValue else { return .system } + + let normalized = normalizeLanguageCode(storedValue) + if normalized == systemCode { + return .system + } + + if supportedLanguageCodes.contains(normalized) { + return AppLanguage(code: normalized) + } + + return .system + } + + static func resolvedLanguageCode(for selectedLanguage: AppLanguage) -> String { + if !selectedLanguage.isSystem { + return selectedLanguage.code + } + + for preferred in Locale.preferredLanguages { + let base = normalizeLanguageCode(preferred) + if supportedLanguageCodes.contains(base) { + return base + } + } + + return fallbackLanguageCode ?? availableLanguageCodes.first ?? systemCode + } + + static func isValidStorageValue(_ storedValue: String) -> Bool { + let normalized = normalizeLanguageCode(storedValue) + return normalized == systemCode || supportedLanguageCodes.contains(normalized) + } + + private static var availableLanguageCodes: [String] { + let normalizedCodes = Bundle.module.localizations + .map(normalizeLanguageCode) + .filter { !$0.isEmpty && $0 != "base" && $0 != systemCode } + + let unique = Array(Set(normalizedCodes)) + return unique.sorted { localizedName(for: $0) < localizedName(for: $1) } + } + + private static func normalizeLanguageCode(_ identifier: String) -> String { + let normalized = identifier.replacingOccurrences(of: "_", with: "-").lowercased() + if normalized == systemCode { + return systemCode + } + + return String(normalized.split(separator: "-").first ?? Substring(normalized)) + } + + private static func localizedName(for code: String) -> String { + let locale = Locale.current + let localized = locale.localizedString(forLanguageCode: code) + ?? Locale(identifier: code).localizedString(forLanguageCode: code) + ?? code + + return localized.capitalized(with: locale) + } + + private static func flagEmoji(forLanguageCode languageCode: String) -> String? { + guard #available(macOS 13.0, *) else { return nil } + + let maximal = Locale.Language(identifier: languageCode).maximalIdentifier + let parts = maximal.split(separator: "-") + + guard let region = parts.last, region.count == 2 else { + return nil + } + + return flagEmoji(fromRegionCode: String(region)) + } + + private static func flagEmoji(fromRegionCode regionCode: String) -> String? { + let upper = regionCode.uppercased() + guard upper.count == 2, upper.unicodeScalars.allSatisfy({ $0.value >= 65 && $0.value <= 90 }) else { + return nil + } + + let base: UInt32 = 127_397 + var scalars = String.UnicodeScalarView() + + for scalar in upper.unicodeScalars { + guard let regional = UnicodeScalar(base + scalar.value) else { + return nil + } + scalars.append(regional) + } + + return String(scalars) + } +} diff --git a/Sources/Aether/App/Translate.swift b/Sources/Aether/App/Translate.swift new file mode 100644 index 0000000..8fcd2d6 --- /dev/null +++ b/Sources/Aether/App/Translate.swift @@ -0,0 +1,30 @@ +import Foundation + +func translate(_ key: String) -> String { + let rawLanguage = UserDefaults.standard.string(forKey: "appLanguage") + let selectedLanguage = AppLanguage.fromStored(rawLanguage) + let languageCode = AppLanguage.resolvedLanguageCode(for: selectedLanguage) + + let localized = localizationBundle(for: languageCode) + .localizedString(forKey: key, value: nil, table: "Localizable") + + if localized != key { + return localized + } + + if let fallbackCode = AppLanguage.fallbackLanguageCode, fallbackCode != languageCode { + return localizationBundle(for: fallbackCode) + .localizedString(forKey: key, value: key, table: "Localizable") + } + + return key +} + +private func localizationBundle(for languageCode: String) -> Bundle { + guard let path = Bundle.module.path(forResource: languageCode, ofType: "lproj"), + let bundle = Bundle(path: path) else { + return .module + } + + return bundle +} diff --git a/Sources/Aether/Resources/de.lproj/Localizable.strings b/Sources/Aether/Resources/de.lproj/Localizable.strings new file mode 100644 index 0000000..c66c63c --- /dev/null +++ b/Sources/Aether/Resources/de.lproj/Localizable.strings @@ -0,0 +1,107 @@ +"common.error" = "Fehler"; +"common.ok" = "OK"; +"common.cancel" = "Abbrechen"; +"common.go" = "Los"; +"common.unknownError" = "Unbekannter Fehler"; + +"menu.file.openBinary" = "Binärdatei öffnen..."; +"menu.file.openProject" = "Projekt öffnen..."; +"menu.file.saveBinaryAs" = "Binärdatei speichern unter..."; +"menu.file.saveProjectAs" = "Projekt speichern unter..."; +"menu.file.close" = "Schließen"; +"menu.edit.undo" = "Rückgängig"; +"menu.edit.redo" = "Wiederholen"; +"menu.analysis.title" = "Analyse"; +"menu.analysis.analyzeAll" = "Alles analysieren"; +"menu.analysis.findFunctions" = "Funktionen finden"; +"menu.analysis.showCFG" = "CFG anzeigen"; +"menu.analysis.decompile" = "Dekompilieren"; +"menu.analysis.generatePseudoCode" = "Pseudocode generieren"; +"menu.analysis.callGraph" = "Aufrufgraph"; +"menu.analysis.cryptoDetection" = "Krypto-Erkennung"; +"menu.analysis.deobfuscation" = "Deobfuskationsanalyse"; +"menu.analysis.typeRecovery" = "Typrekonstruktion"; +"menu.analysis.idiomRecognition" = "Idiom-Erkennung"; +"menu.analysis.showJumpTable" = "Sprungtabelle anzeigen"; +"menu.export.title" = "Export"; +"menu.export.ida" = "Nach IDA Python exportieren..."; +"menu.export.ghidra" = "Nach Ghidra XML exportieren..."; +"menu.export.radare2" = "Nach Radare2 exportieren..."; +"menu.export.binaryNinja" = "Nach Binary Ninja exportieren..."; +"menu.export.json" = "Als JSON exportieren..."; +"menu.export.csv" = "Als CSV exportieren..."; +"menu.export.html" = "Als HTML-Bericht exportieren..."; +"menu.export.markdown" = "Als Markdown exportieren..."; +"menu.export.cheader" = "C-Header exportieren..."; +"menu.navigate.title" = "Navigation"; +"menu.navigate.gotoAddress" = "Gehe zu Adresse..."; +"menu.navigate.search" = "Suchen..."; + +"settings.tab.general" = "Allgemein"; +"settings.tab.appearance" = "Darstellung"; +"settings.tab.analysis" = "Analyse"; +"settings.tab.ai" = "KI"; +"settings.general.language" = "Sprache"; +"settings.language.system" = "System"; +"settings.language.english" = "Englisch"; +"settings.language.french" = "Französisch"; +"settings.language.german" = "Deutsch"; +"settings.language.italian" = "Italienisch"; +"settings.language.spanish" = "Spanisch"; +"settings.general.autoAnalyze" = "Beim Öffnen automatisch analysieren"; +"settings.general.showHexView" = "Hex-Ansicht standardmäßig anzeigen"; +"settings.appearance.font" = "Schriftart"; +"settings.appearance.fontSize" = "Schriftgröße"; +"settings.appearance.fontSizeValue %@" = "Schriftgröße: %@"; +"settings.analysis.deepAnalysis" = "Tiefenanalyse (langsamer)"; +"settings.analysis.analyzeStrings" = "Strings analysieren"; +"settings.analysis.analyzeXrefs" = "Querverweise analysieren"; + +"settings.ai.title" = "KI-Sicherheitsanalyse"; +"settings.ai.subtitle" = "Code mit KI auf Schwachstellen analysieren"; +"settings.ai.active" = "Aktiv"; +"settings.ai.apiKey" = "API-Schlüssel"; +"settings.ai.apiKeyPlaceholder" = "sk-ant-..."; +"settings.ai.apiKeyHelp" = "API-Schlüssel unter console.anthropic.com abrufen"; +"settings.ai.saveKey" = "Schlüssel speichern"; +"settings.ai.remove" = "Entfernen"; +"settings.ai.saved" = "Gespeichert!"; +"settings.ai.info.analyzes" = "Analysiert Code auf Sicherheitslücken"; +"settings.ai.info.keychain" = "API-Schlüssel wird im macOS-Schlüsselbund gespeichert"; +"settings.ai.info.credits" = "Verwendet Ihre API-Guthaben"; +"settings.ai.error.invalidFormat" = "Ungültiges Format"; +"settings.ai.error.saveFailed" = "Speichern fehlgeschlagen"; + +"welcome.title" = "Aether"; +"welcome.subtitle" = "Ziehen Sie eine Binärdatei hierher\noder verwenden Sie Datei → Binärdatei öffnen (⌘O)"; +"goto.title" = "Gehe zu Adresse"; +"goto.addressPlaceholder" = "Adresse (hex)"; + +"toolbar.open" = "Öffnen"; +"toolbar.save" = "Speichern"; +"toolbar.reload" = "Neu laden"; +"toolbar.close" = "Schließen"; +"toolbar.unsavedChanges" = "Ungespeicherte Änderungen"; +"toolbar.analyze" = "Analysieren"; +"toolbar.functions" = "Funktionen"; +"toolbar.decompiler" = "Dekompilierer"; +"toolbar.hexView" = "Hex-Ansicht"; +"toolbar.cfg" = "CFG"; +"toolbar.ai" = "KI"; +"toolbar.ai.chat" = "Mit KI chatten..."; +"toolbar.ai.explainFunction" = "Funktion erklären"; +"toolbar.ai.renameVariables" = "Variablen umbenennen"; +"toolbar.ai.securityAnalysis" = "Sicherheitsanalyse"; +"toolbar.ai.analyzeBinary" = "Binärdatei analysieren"; +"toolbar.ai.configure" = "API-Schlüssel in den Einstellungen konfigurieren"; +"toolbar.settings" = "Einstellungen"; +"toolbar.frida" = "Frida"; +"toolbar.frida.generateBasic" = "Basis-Skript generieren"; +"toolbar.frida.generateWithAI" = "Mit KI generieren"; +"toolbar.frida.hookMultiple" = "Mehrere Funktionen hooken"; +"toolbar.frida.platform" = "Plattform"; +"toolbar.frida.hookType" = "Hook-Typ"; +"toolbar.search" = "Suchen..."; +"toolbar.goto" = "Gehe zu"; + +"export.defaultFileName" = "export"; diff --git a/Sources/Aether/Resources/en.lproj/Localizable.strings b/Sources/Aether/Resources/en.lproj/Localizable.strings new file mode 100644 index 0000000..2c2eb46 --- /dev/null +++ b/Sources/Aether/Resources/en.lproj/Localizable.strings @@ -0,0 +1,107 @@ +"common.error" = "Error"; +"common.ok" = "OK"; +"common.cancel" = "Cancel"; +"common.go" = "Go"; +"common.unknownError" = "Unknown error"; + +"menu.file.openBinary" = "Open Binary..."; +"menu.file.openProject" = "Open Project..."; +"menu.file.saveBinaryAs" = "Save Binary As..."; +"menu.file.saveProjectAs" = "Save Project As..."; +"menu.file.close" = "Close"; +"menu.edit.undo" = "Undo"; +"menu.edit.redo" = "Redo"; +"menu.analysis.title" = "Analysis"; +"menu.analysis.analyzeAll" = "Analyze All"; +"menu.analysis.findFunctions" = "Find Functions"; +"menu.analysis.showCFG" = "Show CFG"; +"menu.analysis.decompile" = "Decompile"; +"menu.analysis.generatePseudoCode" = "Generate Pseudo-Code"; +"menu.analysis.callGraph" = "Call Graph"; +"menu.analysis.cryptoDetection" = "Crypto Detection"; +"menu.analysis.deobfuscation" = "Deobfuscation Analysis"; +"menu.analysis.typeRecovery" = "Type Recovery"; +"menu.analysis.idiomRecognition" = "Idiom Recognition"; +"menu.analysis.showJumpTable" = "Show Jump Table"; +"menu.export.title" = "Export"; +"menu.export.ida" = "Export to IDA Python..."; +"menu.export.ghidra" = "Export to Ghidra XML..."; +"menu.export.radare2" = "Export to Radare2..."; +"menu.export.binaryNinja" = "Export to Binary Ninja..."; +"menu.export.json" = "Export to JSON..."; +"menu.export.csv" = "Export to CSV..."; +"menu.export.html" = "Export to HTML Report..."; +"menu.export.markdown" = "Export to Markdown..."; +"menu.export.cheader" = "Export C Header..."; +"menu.navigate.title" = "Navigate"; +"menu.navigate.gotoAddress" = "Go to Address..."; +"menu.navigate.search" = "Search..."; + +"settings.tab.general" = "General"; +"settings.tab.appearance" = "Appearance"; +"settings.tab.analysis" = "Analysis"; +"settings.tab.ai" = "AI"; +"settings.general.language" = "Language"; +"settings.language.system" = "System"; +"settings.language.english" = "English"; +"settings.language.french" = "French"; +"settings.language.german" = "German"; +"settings.language.italian" = "Italian"; +"settings.language.spanish" = "Spanish"; +"settings.general.autoAnalyze" = "Auto-analyze on file open"; +"settings.general.showHexView" = "Show Hex View by default"; +"settings.appearance.font" = "Font"; +"settings.appearance.fontSize" = "Font Size"; +"settings.appearance.fontSizeValue %@" = "Font Size: %@"; +"settings.analysis.deepAnalysis" = "Deep analysis (slower)"; +"settings.analysis.analyzeStrings" = "Analyze strings"; +"settings.analysis.analyzeXrefs" = "Analyze cross-references"; + +"settings.ai.title" = "AI Security Analysis"; +"settings.ai.subtitle" = "Analyze code for vulnerabilities with AI"; +"settings.ai.active" = "Active"; +"settings.ai.apiKey" = "API Key"; +"settings.ai.apiKeyPlaceholder" = "sk-ant-..."; +"settings.ai.apiKeyHelp" = "Get your API key from console.anthropic.com"; +"settings.ai.saveKey" = "Save Key"; +"settings.ai.remove" = "Remove"; +"settings.ai.saved" = "Saved!"; +"settings.ai.info.analyzes" = "Analyzes code for security vulnerabilities"; +"settings.ai.info.keychain" = "API key stored in macOS Keychain"; +"settings.ai.info.credits" = "Uses your API credits"; +"settings.ai.error.invalidFormat" = "Invalid format"; +"settings.ai.error.saveFailed" = "Save failed"; + +"welcome.title" = "Aether"; +"welcome.subtitle" = "Drag and drop a binary file here\nor use File → Open Binary (⌘O)"; +"goto.title" = "Go to Address"; +"goto.addressPlaceholder" = "Address (hex)"; + +"toolbar.open" = "Open"; +"toolbar.save" = "Save"; +"toolbar.reload" = "Reload"; +"toolbar.close" = "Close"; +"toolbar.unsavedChanges" = "Unsaved changes"; +"toolbar.analyze" = "Analyze"; +"toolbar.functions" = "Functions"; +"toolbar.decompiler" = "Decompiler"; +"toolbar.hexView" = "Hex View"; +"toolbar.cfg" = "CFG"; +"toolbar.ai" = "AI"; +"toolbar.ai.chat" = "Chat with AI..."; +"toolbar.ai.explainFunction" = "Explain Function"; +"toolbar.ai.renameVariables" = "Rename Variables"; +"toolbar.ai.securityAnalysis" = "Security Analysis"; +"toolbar.ai.analyzeBinary" = "Analyze Binary"; +"toolbar.ai.configure" = "Configure API key in Settings"; +"toolbar.settings" = "Settings"; +"toolbar.frida" = "Frida"; +"toolbar.frida.generateBasic" = "Generate Basic Script"; +"toolbar.frida.generateWithAI" = "Generate with AI"; +"toolbar.frida.hookMultiple" = "Hook Multiple Functions"; +"toolbar.frida.platform" = "Platform"; +"toolbar.frida.hookType" = "Hook Type"; +"toolbar.search" = "Search..."; +"toolbar.goto" = "Go to"; + +"export.defaultFileName" = "export"; diff --git a/Sources/Aether/Resources/es.lproj/Localizable.strings b/Sources/Aether/Resources/es.lproj/Localizable.strings new file mode 100644 index 0000000..419fa98 --- /dev/null +++ b/Sources/Aether/Resources/es.lproj/Localizable.strings @@ -0,0 +1,107 @@ +"common.error" = "Error"; +"common.ok" = "OK"; +"common.cancel" = "Cancelar"; +"common.go" = "Ir"; +"common.unknownError" = "Error desconocido"; + +"menu.file.openBinary" = "Abrir binario..."; +"menu.file.openProject" = "Abrir proyecto..."; +"menu.file.saveBinaryAs" = "Guardar binario como..."; +"menu.file.saveProjectAs" = "Guardar proyecto como..."; +"menu.file.close" = "Cerrar"; +"menu.edit.undo" = "Deshacer"; +"menu.edit.redo" = "Rehacer"; +"menu.analysis.title" = "Análisis"; +"menu.analysis.analyzeAll" = "Analizar todo"; +"menu.analysis.findFunctions" = "Buscar funciones"; +"menu.analysis.showCFG" = "Mostrar CFG"; +"menu.analysis.decompile" = "Decompilar"; +"menu.analysis.generatePseudoCode" = "Generar pseudo-código"; +"menu.analysis.callGraph" = "Grafo de llamadas"; +"menu.analysis.cryptoDetection" = "Detección criptográfica"; +"menu.analysis.deobfuscation" = "Análisis de desofuscación"; +"menu.analysis.typeRecovery" = "Recuperación de tipos"; +"menu.analysis.idiomRecognition" = "Reconocimiento de patrones"; +"menu.analysis.showJumpTable" = "Mostrar tabla de saltos"; +"menu.export.title" = "Exportar"; +"menu.export.ida" = "Exportar a IDA Python..."; +"menu.export.ghidra" = "Exportar a Ghidra XML..."; +"menu.export.radare2" = "Exportar a Radare2..."; +"menu.export.binaryNinja" = "Exportar a Binary Ninja..."; +"menu.export.json" = "Exportar a JSON..."; +"menu.export.csv" = "Exportar a CSV..."; +"menu.export.html" = "Exportar informe HTML..."; +"menu.export.markdown" = "Exportar a Markdown..."; +"menu.export.cheader" = "Exportar cabecera C..."; +"menu.navigate.title" = "Navegar"; +"menu.navigate.gotoAddress" = "Ir a dirección..."; +"menu.navigate.search" = "Buscar..."; + +"settings.tab.general" = "General"; +"settings.tab.appearance" = "Apariencia"; +"settings.tab.analysis" = "Análisis"; +"settings.tab.ai" = "IA"; +"settings.general.language" = "Idioma"; +"settings.language.system" = "Sistema"; +"settings.language.english" = "Inglés"; +"settings.language.french" = "Francés"; +"settings.language.german" = "Alemán"; +"settings.language.italian" = "Italiano"; +"settings.language.spanish" = "Español"; +"settings.general.autoAnalyze" = "Autoanalizar al abrir archivo"; +"settings.general.showHexView" = "Mostrar vista hex por defecto"; +"settings.appearance.font" = "Fuente"; +"settings.appearance.fontSize" = "Tamaño de fuente"; +"settings.appearance.fontSizeValue %@" = "Tamaño de fuente: %@"; +"settings.analysis.deepAnalysis" = "Análisis profundo (más lento)"; +"settings.analysis.analyzeStrings" = "Analizar cadenas"; +"settings.analysis.analyzeXrefs" = "Analizar referencias cruzadas"; + +"settings.ai.title" = "Análisis de seguridad con IA"; +"settings.ai.subtitle" = "Analiza código en busca de vulnerabilidades con IA"; +"settings.ai.active" = "Activo"; +"settings.ai.apiKey" = "Clave API"; +"settings.ai.apiKeyPlaceholder" = "sk-ant-..."; +"settings.ai.apiKeyHelp" = "Obtén tu clave API en console.anthropic.com"; +"settings.ai.saveKey" = "Guardar clave"; +"settings.ai.remove" = "Eliminar"; +"settings.ai.saved" = "¡Guardado!"; +"settings.ai.info.analyzes" = "Analiza código para detectar vulnerabilidades"; +"settings.ai.info.keychain" = "Clave API almacenada en Llavero de macOS"; +"settings.ai.info.credits" = "Usa tus créditos de API"; +"settings.ai.error.invalidFormat" = "Formato inválido"; +"settings.ai.error.saveFailed" = "Error al guardar"; + +"welcome.title" = "Aether"; +"welcome.subtitle" = "Arrastra y suelta un binario aquí\no usa Archivo → Abrir binario (⌘O)"; +"goto.title" = "Ir a dirección"; +"goto.addressPlaceholder" = "Dirección (hex)"; + +"toolbar.open" = "Abrir"; +"toolbar.save" = "Guardar"; +"toolbar.reload" = "Recargar"; +"toolbar.close" = "Cerrar"; +"toolbar.unsavedChanges" = "Cambios sin guardar"; +"toolbar.analyze" = "Analizar"; +"toolbar.functions" = "Funciones"; +"toolbar.decompiler" = "Decompilador"; +"toolbar.hexView" = "Vista Hex"; +"toolbar.cfg" = "CFG"; +"toolbar.ai" = "IA"; +"toolbar.ai.chat" = "Chatear con IA..."; +"toolbar.ai.explainFunction" = "Explicar función"; +"toolbar.ai.renameVariables" = "Renombrar variables"; +"toolbar.ai.securityAnalysis" = "Análisis de seguridad"; +"toolbar.ai.analyzeBinary" = "Analizar binario"; +"toolbar.ai.configure" = "Configurar clave API en Ajustes"; +"toolbar.settings" = "Ajustes"; +"toolbar.frida" = "Frida"; +"toolbar.frida.generateBasic" = "Generar script básico"; +"toolbar.frida.generateWithAI" = "Generar con IA"; +"toolbar.frida.hookMultiple" = "Hook de múltiples funciones"; +"toolbar.frida.platform" = "Plataforma"; +"toolbar.frida.hookType" = "Tipo de hook"; +"toolbar.search" = "Buscar..."; +"toolbar.goto" = "Ir a"; + +"export.defaultFileName" = "export"; diff --git a/Sources/Aether/Resources/fr.lproj/Localizable.strings b/Sources/Aether/Resources/fr.lproj/Localizable.strings new file mode 100644 index 0000000..cad5d0c --- /dev/null +++ b/Sources/Aether/Resources/fr.lproj/Localizable.strings @@ -0,0 +1,107 @@ +"common.error" = "Erreur"; +"common.ok" = "OK"; +"common.cancel" = "Annuler"; +"common.go" = "Aller"; +"common.unknownError" = "Erreur inconnue"; + +"menu.file.openBinary" = "Ouvrir un binaire..."; +"menu.file.openProject" = "Ouvrir un projet..."; +"menu.file.saveBinaryAs" = "Enregistrer le binaire sous..."; +"menu.file.saveProjectAs" = "Enregistrer le projet sous..."; +"menu.file.close" = "Fermer"; +"menu.edit.undo" = "Annuler"; +"menu.edit.redo" = "Rétablir"; +"menu.analysis.title" = "Analyse"; +"menu.analysis.analyzeAll" = "Analyser tout"; +"menu.analysis.findFunctions" = "Trouver les fonctions"; +"menu.analysis.showCFG" = "Afficher le CFG"; +"menu.analysis.decompile" = "Décompiler"; +"menu.analysis.generatePseudoCode" = "Générer le pseudo-code"; +"menu.analysis.callGraph" = "Graphe d'appels"; +"menu.analysis.cryptoDetection" = "Détection crypto"; +"menu.analysis.deobfuscation" = "Analyse de désobfuscation"; +"menu.analysis.typeRecovery" = "Récupération de types"; +"menu.analysis.idiomRecognition" = "Reconnaissance d'idiomes"; +"menu.analysis.showJumpTable" = "Afficher la table des sauts"; +"menu.export.title" = "Exporter"; +"menu.export.ida" = "Exporter vers IDA Python..."; +"menu.export.ghidra" = "Exporter vers Ghidra XML..."; +"menu.export.radare2" = "Exporter vers Radare2..."; +"menu.export.binaryNinja" = "Exporter vers Binary Ninja..."; +"menu.export.json" = "Exporter en JSON..."; +"menu.export.csv" = "Exporter en CSV..."; +"menu.export.html" = "Exporter en rapport HTML..."; +"menu.export.markdown" = "Exporter en Markdown..."; +"menu.export.cheader" = "Exporter l'en-tête C..."; +"menu.navigate.title" = "Navigation"; +"menu.navigate.gotoAddress" = "Aller à l'adresse..."; +"menu.navigate.search" = "Rechercher..."; + +"settings.tab.general" = "Général"; +"settings.tab.appearance" = "Apparence"; +"settings.tab.analysis" = "Analyse"; +"settings.tab.ai" = "IA"; +"settings.general.language" = "Langue"; +"settings.language.system" = "Système"; +"settings.language.english" = "Anglais"; +"settings.language.french" = "Français"; +"settings.language.german" = "Allemand"; +"settings.language.italian" = "Italien"; +"settings.language.spanish" = "Espagnol"; +"settings.general.autoAnalyze" = "Analyser automatiquement à l'ouverture"; +"settings.general.showHexView" = "Afficher la vue hexadécimale par défaut"; +"settings.appearance.font" = "Police"; +"settings.appearance.fontSize" = "Taille de police"; +"settings.appearance.fontSizeValue %@" = "Taille de police : %@"; +"settings.analysis.deepAnalysis" = "Analyse approfondie (plus lente)"; +"settings.analysis.analyzeStrings" = "Analyser les chaînes"; +"settings.analysis.analyzeXrefs" = "Analyser les références croisées"; + +"settings.ai.title" = "Analyse de sécurité IA"; +"settings.ai.subtitle" = "Analyser le code pour détecter des vulnérabilités avec l'IA"; +"settings.ai.active" = "Actif"; +"settings.ai.apiKey" = "Clé API"; +"settings.ai.apiKeyPlaceholder" = "sk-ant-..."; +"settings.ai.apiKeyHelp" = "Récupérez votre clé API sur console.anthropic.com"; +"settings.ai.saveKey" = "Enregistrer la clé"; +"settings.ai.remove" = "Supprimer"; +"settings.ai.saved" = "Enregistré !"; +"settings.ai.info.analyzes" = "Analyse le code pour les vulnérabilités"; +"settings.ai.info.keychain" = "Clé API stockée dans le trousseau macOS"; +"settings.ai.info.credits" = "Utilise vos crédits API"; +"settings.ai.error.invalidFormat" = "Format invalide"; +"settings.ai.error.saveFailed" = "Échec de l'enregistrement"; + +"welcome.title" = "Aether"; +"welcome.subtitle" = "Glissez-déposez un fichier binaire ici\nou utilisez Fichier → Ouvrir un binaire (⌘O)"; +"goto.title" = "Aller à l'adresse"; +"goto.addressPlaceholder" = "Adresse (hex)"; + +"toolbar.open" = "Ouvrir"; +"toolbar.save" = "Enregistrer"; +"toolbar.reload" = "Recharger"; +"toolbar.close" = "Fermer"; +"toolbar.unsavedChanges" = "Modifications non enregistrées"; +"toolbar.analyze" = "Analyser"; +"toolbar.functions" = "Fonctions"; +"toolbar.decompiler" = "Décompilateur"; +"toolbar.hexView" = "Vue Hex"; +"toolbar.cfg" = "CFG"; +"toolbar.ai" = "IA"; +"toolbar.ai.chat" = "Discuter avec l'IA..."; +"toolbar.ai.explainFunction" = "Expliquer la fonction"; +"toolbar.ai.renameVariables" = "Renommer les variables"; +"toolbar.ai.securityAnalysis" = "Analyse de sécurité"; +"toolbar.ai.analyzeBinary" = "Analyser le binaire"; +"toolbar.ai.configure" = "Configurer la clé API dans Réglages"; +"toolbar.settings" = "Réglages"; +"toolbar.frida" = "Frida"; +"toolbar.frida.generateBasic" = "Générer un script de base"; +"toolbar.frida.generateWithAI" = "Générer avec l'IA"; +"toolbar.frida.hookMultiple" = "Hooker plusieurs fonctions"; +"toolbar.frida.platform" = "Plateforme"; +"toolbar.frida.hookType" = "Type de hook"; +"toolbar.search" = "Rechercher..."; +"toolbar.goto" = "Aller à"; + +"export.defaultFileName" = "export"; diff --git a/Sources/Aether/Resources/it.lproj/Localizable.strings b/Sources/Aether/Resources/it.lproj/Localizable.strings new file mode 100644 index 0000000..da8a82c --- /dev/null +++ b/Sources/Aether/Resources/it.lproj/Localizable.strings @@ -0,0 +1,107 @@ +"common.error" = "Errore"; +"common.ok" = "OK"; +"common.cancel" = "Annulla"; +"common.go" = "Vai"; +"common.unknownError" = "Errore sconosciuto"; + +"menu.file.openBinary" = "Apri binario..."; +"menu.file.openProject" = "Apri progetto..."; +"menu.file.saveBinaryAs" = "Salva binario con nome..."; +"menu.file.saveProjectAs" = "Salva progetto con nome..."; +"menu.file.close" = "Chiudi"; +"menu.edit.undo" = "Annulla"; +"menu.edit.redo" = "Ripeti"; +"menu.analysis.title" = "Analisi"; +"menu.analysis.analyzeAll" = "Analizza tutto"; +"menu.analysis.findFunctions" = "Trova funzioni"; +"menu.analysis.showCFG" = "Mostra CFG"; +"menu.analysis.decompile" = "Decompila"; +"menu.analysis.generatePseudoCode" = "Genera pseudo-codice"; +"menu.analysis.callGraph" = "Grafo delle chiamate"; +"menu.analysis.cryptoDetection" = "Rilevamento crittografia"; +"menu.analysis.deobfuscation" = "Analisi di deoffuscamento"; +"menu.analysis.typeRecovery" = "Recupero tipi"; +"menu.analysis.idiomRecognition" = "Riconoscimento idiomi"; +"menu.analysis.showJumpTable" = "Mostra tabella salti"; +"menu.export.title" = "Esporta"; +"menu.export.ida" = "Esporta in IDA Python..."; +"menu.export.ghidra" = "Esporta in Ghidra XML..."; +"menu.export.radare2" = "Esporta in Radare2..."; +"menu.export.binaryNinja" = "Esporta in Binary Ninja..."; +"menu.export.json" = "Esporta in JSON..."; +"menu.export.csv" = "Esporta in CSV..."; +"menu.export.html" = "Esporta in report HTML..."; +"menu.export.markdown" = "Esporta in Markdown..."; +"menu.export.cheader" = "Esporta header C..."; +"menu.navigate.title" = "Naviga"; +"menu.navigate.gotoAddress" = "Vai all'indirizzo..."; +"menu.navigate.search" = "Cerca..."; + +"settings.tab.general" = "Generale"; +"settings.tab.appearance" = "Aspetto"; +"settings.tab.analysis" = "Analisi"; +"settings.tab.ai" = "IA"; +"settings.general.language" = "Lingua"; +"settings.language.system" = "Sistema"; +"settings.language.english" = "Inglese"; +"settings.language.french" = "Francese"; +"settings.language.german" = "Tedesco"; +"settings.language.italian" = "Italiano"; +"settings.language.spanish" = "Spagnolo"; +"settings.general.autoAnalyze" = "Analizza automaticamente all'apertura"; +"settings.general.showHexView" = "Mostra vista esadecimale per default"; +"settings.appearance.font" = "Font"; +"settings.appearance.fontSize" = "Dimensione font"; +"settings.appearance.fontSizeValue %@" = "Dimensione font: %@"; +"settings.analysis.deepAnalysis" = "Analisi approfondita (più lenta)"; +"settings.analysis.analyzeStrings" = "Analizza stringhe"; +"settings.analysis.analyzeXrefs" = "Analizza riferimenti incrociati"; + +"settings.ai.title" = "Analisi di sicurezza IA"; +"settings.ai.subtitle" = "Analizza il codice per vulnerabilità con IA"; +"settings.ai.active" = "Attivo"; +"settings.ai.apiKey" = "Chiave API"; +"settings.ai.apiKeyPlaceholder" = "sk-ant-..."; +"settings.ai.apiKeyHelp" = "Ottieni la tua chiave API da console.anthropic.com"; +"settings.ai.saveKey" = "Salva chiave"; +"settings.ai.remove" = "Rimuovi"; +"settings.ai.saved" = "Salvata!"; +"settings.ai.info.analyzes" = "Analizza il codice per vulnerabilità di sicurezza"; +"settings.ai.info.keychain" = "Chiave API salvata nel Portachiavi macOS"; +"settings.ai.info.credits" = "Usa i tuoi crediti API"; +"settings.ai.error.invalidFormat" = "Formato non valido"; +"settings.ai.error.saveFailed" = "Salvataggio non riuscito"; + +"welcome.title" = "Aether"; +"welcome.subtitle" = "Trascina qui un file binario\no usa File → Apri binario (⌘O)"; +"goto.title" = "Vai all'indirizzo"; +"goto.addressPlaceholder" = "Indirizzo (hex)"; + +"toolbar.open" = "Apri"; +"toolbar.save" = "Salva"; +"toolbar.reload" = "Ricarica"; +"toolbar.close" = "Chiudi"; +"toolbar.unsavedChanges" = "Modifiche non salvate"; +"toolbar.analyze" = "Analizza"; +"toolbar.functions" = "Funzioni"; +"toolbar.decompiler" = "Decompilatore"; +"toolbar.hexView" = "Vista Hex"; +"toolbar.cfg" = "CFG"; +"toolbar.ai" = "IA"; +"toolbar.ai.chat" = "Chatta con IA..."; +"toolbar.ai.explainFunction" = "Spiega funzione"; +"toolbar.ai.renameVariables" = "Rinomina variabili"; +"toolbar.ai.securityAnalysis" = "Analisi di sicurezza"; +"toolbar.ai.analyzeBinary" = "Analizza binario"; +"toolbar.ai.configure" = "Configura la chiave API nelle Impostazioni"; +"toolbar.settings" = "Impostazioni"; +"toolbar.frida" = "Frida"; +"toolbar.frida.generateBasic" = "Genera script base"; +"toolbar.frida.generateWithAI" = "Genera con IA"; +"toolbar.frida.hookMultiple" = "Hook di più funzioni"; +"toolbar.frida.platform" = "Piattaforma"; +"toolbar.frida.hookType" = "Tipo di hook"; +"toolbar.search" = "Cerca..."; +"toolbar.goto" = "Vai a"; + +"export.defaultFileName" = "export"; diff --git a/Sources/Aether/UI/MainWindow/MainView.swift b/Sources/Aether/UI/MainWindow/MainView.swift index ce587d0..88afb73 100644 --- a/Sources/Aether/UI/MainWindow/MainView.swift +++ b/Sources/Aether/UI/MainWindow/MainView.swift @@ -134,12 +134,12 @@ struct MainView: View { WelcomeView() } } - .alert("Error", isPresented: $appState.showError) { - Button("OK") { + .alert(translate("common.error"), isPresented: $appState.showError) { + Button(translate("common.ok")) { appState.showError = false } } message: { - Text(appState.errorMessage ?? "Unknown error") + Text(appState.errorMessage ?? translate("common.unknownError")) } } @@ -175,11 +175,11 @@ struct WelcomeView: View { .font(.system(size: 64)) .foregroundColor(.secondary) - Text("Aether") + Text(translate("welcome.title")) .font(.largeTitle) .fontWeight(.bold) - Text("Drag and drop a binary file here\nor use File → Open Binary (⌘O)") + Text(translate("welcome.subtitle")) .multilineTextAlignment(.center) .foregroundColor(.secondary) @@ -253,10 +253,10 @@ struct GoToAddressSheet: View { var body: some View { VStack(spacing: 16) { - Text("Go to Address") + Text(translate("goto.title")) .font(.headline) - TextField("Address (hex)", text: $addressText) + TextField(translate("goto.addressPlaceholder"), text: $addressText) .textFieldStyle(.roundedBorder) .focused($isFocused) .onSubmit { @@ -264,12 +264,12 @@ struct GoToAddressSheet: View { } HStack { - Button("Cancel") { + Button(translate("common.cancel")) { dismiss() } .keyboardShortcut(.cancelAction) - Button("Go") { + Button(translate("common.go")) { goToAddress() } .keyboardShortcut(.defaultAction) diff --git a/Sources/Aether/UI/MainWindow/ToolbarView.swift b/Sources/Aether/UI/MainWindow/ToolbarView.swift index bc254b9..4a7ff81 100644 --- a/Sources/Aether/UI/MainWindow/ToolbarView.swift +++ b/Sources/Aether/UI/MainWindow/ToolbarView.swift @@ -10,7 +10,7 @@ struct ToolbarView: View { HStack(spacing: 8) { ToolbarButton( icon: "doc.badge.plus", - label: "Open", + label: translate("toolbar.open"), shortcut: "O" ) { appState.openFile() @@ -18,7 +18,7 @@ struct ToolbarView: View { ToolbarButton( icon: "square.and.arrow.down", - label: "Save", + label: translate("toolbar.save"), shortcut: "S" ) { appState.saveFileAs() @@ -27,7 +27,7 @@ struct ToolbarView: View { ToolbarButton( icon: "arrow.clockwise", - label: "Reload", + label: translate("toolbar.reload"), shortcut: nil ) { if let url = appState.currentFile?.url { @@ -40,7 +40,7 @@ struct ToolbarView: View { ToolbarButton( icon: "xmark.circle", - label: "Close", + label: translate("toolbar.close"), shortcut: "W" ) { appState.closeFile() @@ -53,7 +53,7 @@ struct ToolbarView: View { Circle() .fill(Color.orange) .frame(width: 8, height: 8) - .help("Unsaved changes") + .help(translate("toolbar.unsavedChanges")) } Divider() @@ -63,7 +63,7 @@ struct ToolbarView: View { HStack(spacing: 8) { ToolbarButton( icon: "cpu", - label: "Analyze", + label: translate("toolbar.analyze"), shortcut: "A" ) { appState.analyzeAll() @@ -72,7 +72,7 @@ struct ToolbarView: View { ToolbarButton( icon: "function", - label: "Functions", + label: translate("toolbar.functions"), shortcut: nil ) { appState.findFunctions() @@ -87,19 +87,19 @@ struct ToolbarView: View { HStack(spacing: 8) { ToolbarToggle( icon: "rectangle.split.2x1", - label: "Decompiler", + label: translate("toolbar.decompiler"), isOn: $appState.showDecompiler ) ToolbarToggle( icon: "rectangle.bottomhalf.filled", - label: "Hex View", + label: translate("toolbar.hexView"), isOn: $appState.showHexView ) ToolbarButton( icon: "point.3.connected.trianglepath.dotted", - label: "CFG", + label: translate("toolbar.cfg"), shortcut: "G" ) { appState.showCFG.toggle() @@ -118,7 +118,7 @@ struct ToolbarView: View { Button { appState.showAIChat = true } label: { - Label("Chat with AI...", systemImage: "bubble.left.and.bubble.right") + Label(translate("toolbar.ai.chat"), systemImage: "bubble.left.and.bubble.right") } Divider() @@ -127,14 +127,14 @@ struct ToolbarView: View { Button { appState.explainCurrentFunction() } label: { - Label("Explain Function", systemImage: "text.bubble") + Label(translate("toolbar.ai.explainFunction"), systemImage: "text.bubble") } .disabled(appState.selectedFunction == nil) Button { appState.suggestVariableNames() } label: { - Label("Rename Variables", systemImage: "textformat.abc") + Label(translate("toolbar.ai.renameVariables"), systemImage: "textformat.abc") } .disabled(appState.selectedFunction == nil || appState.decompilerOutput.isEmpty) @@ -144,14 +144,14 @@ struct ToolbarView: View { Button { appState.analyzeWithAI() } label: { - Label("Security Analysis", systemImage: "shield.lefthalf.filled") + Label(translate("toolbar.ai.securityAnalysis"), systemImage: "shield.lefthalf.filled") } .disabled(appState.selectedFunction == nil) Button { appState.analyzeBinaryWithAI() } label: { - Label("Analyze Binary", systemImage: "doc.viewfinder") + Label(translate("toolbar.ai.analyzeBinary"), systemImage: "doc.viewfinder") } .disabled(appState.currentFile == nil) } label: { @@ -159,7 +159,7 @@ struct ToolbarView: View { Image(systemName: "brain") .font(.system(size: 16)) .foregroundColor(.purple) - Text("AI") + Text(translate("toolbar.ai")) .font(.caption2) } .frame(minWidth: 40) @@ -170,18 +170,18 @@ struct ToolbarView: View { } else { ToolbarButton( icon: "brain", - label: "AI", + label: translate("toolbar.ai"), shortcut: nil ) { openSettings() } .opacity(0.5) - .help("Configure API key in Settings") + .help(translate("toolbar.ai.configure")) } ToolbarButton( icon: "gear", - label: "Settings", + label: translate("toolbar.settings"), shortcut: "," ) { openSettings() @@ -196,7 +196,7 @@ struct ToolbarView: View { Button { appState.generateFridaScript() } label: { - Label("Generate Basic Script", systemImage: "doc.text") + Label(translate("toolbar.frida.generateBasic"), systemImage: "doc.text") } .disabled(appState.selectedFunction == nil) @@ -204,7 +204,7 @@ struct ToolbarView: View { Button { appState.generateFridaScriptWithAI() } label: { - Label("Generate with AI", systemImage: "brain") + Label(translate("toolbar.frida.generateWithAI"), systemImage: "brain") } .disabled(appState.selectedFunction == nil) } @@ -212,14 +212,14 @@ struct ToolbarView: View { Button { appState.generateMultiFunctionFridaScript() } label: { - Label("Hook Multiple Functions", systemImage: "list.bullet") + Label(translate("toolbar.frida.hookMultiple"), systemImage: "list.bullet") } .disabled(appState.functions.isEmpty) Divider() // Platform submenu - Menu("Platform") { + Menu(translate("toolbar.frida.platform")) { ForEach(FridaPlatform.allCases) { platform in Button { appState.selectedFridaPlatform = platform @@ -235,7 +235,7 @@ struct ToolbarView: View { } // Hook type submenu - Menu("Hook Type") { + Menu(translate("toolbar.frida.hookType")) { ForEach(FridaHookType.allCases) { type in Button { appState.selectedFridaHookType = type @@ -254,7 +254,7 @@ struct ToolbarView: View { Image(systemName: "hammer.fill") .font(.system(size: 16)) .foregroundColor(.orange) - Text("Frida") + Text(translate("toolbar.frida")) .font(.caption2) } .frame(minWidth: 50) @@ -271,7 +271,7 @@ struct ToolbarView: View { Image(systemName: "magnifyingglass") .foregroundColor(.secondary) - Text("Search...") + Text(translate("toolbar.search")) .foregroundColor(.secondary) } .padding(.horizontal, 12) @@ -285,7 +285,7 @@ struct ToolbarView: View { // Quick navigation ToolbarButton( icon: "arrow.right.circle", - label: "Go to", + label: translate("toolbar.goto"), shortcut: "G" ) { appState.showGoToAddress = true @@ -332,7 +332,7 @@ struct ToolbarButton: View { .onHover { hovering in isHovered = hovering } - .help(shortcut != nil ? "\(label) (\(shortcut!))" : label) + .help(shortcut ?? "") } } @@ -367,6 +367,6 @@ struct ToolbarToggle: View { .onHover { hovering in isHovered = hovering } - .help(label) + .help("") } } diff --git a/Sources/Aether/UI/Settings/SettingsView.swift b/Sources/Aether/UI/Settings/SettingsView.swift index f57ea19..5f3655c 100644 --- a/Sources/Aether/UI/Settings/SettingsView.swift +++ b/Sources/Aether/UI/Settings/SettingsView.swift @@ -22,15 +22,15 @@ struct AISettingsTab: View { .font(.title2) .foregroundColor(.purple) VStack(alignment: .leading) { - Text("AI Security Analysis") + Text(translate("settings.ai.title")) .font(.headline) - Text("Analyze code for vulnerabilities with AI") + Text(translate("settings.ai.subtitle")) .font(.caption) .foregroundColor(.secondary) } Spacer() if apiKeyConfigured { - Label("Active", systemImage: "checkmark.circle.fill") + Label(translate("settings.ai.active"), systemImage: "checkmark.circle.fill") .foregroundColor(.green) .font(.caption) } @@ -40,17 +40,17 @@ struct AISettingsTab: View { // API Key Section VStack(alignment: .leading, spacing: 8) { - Text("API Key") + Text(translate("settings.ai.apiKey")) .font(.subheadline) .fontWeight(.medium) HStack { if showAPIKey { - TextField("sk-ant-...", text: $apiKey) + TextField(translate("settings.ai.apiKeyPlaceholder"), text: $apiKey) .textFieldStyle(.roundedBorder) .font(.system(.body, design: .monospaced)) } else { - SecureField("sk-ant-...", text: $apiKey) + SecureField(translate("settings.ai.apiKeyPlaceholder"), text: $apiKey) .textFieldStyle(.roundedBorder) .font(.system(.body, design: .monospaced)) } @@ -63,21 +63,21 @@ struct AISettingsTab: View { .buttonStyle(.plain) } - Text("Get your API key from console.anthropic.com") + Text(translate("settings.ai.apiKeyHelp")) .font(.caption) .foregroundColor(.secondary) } // Actions HStack { - Button("Save Key") { + Button(translate("settings.ai.saveKey")) { saveAPIKey() } .buttonStyle(.borderedProminent) .disabled(apiKey.isEmpty || isValidating) if apiKeyConfigured { - Button("Remove") { + Button(translate("settings.ai.remove")) { removeAPIKey() } .foregroundColor(.red) @@ -92,7 +92,7 @@ struct AISettingsTab: View { switch saveStatus { case .success: - Label("Saved!", systemImage: "checkmark.circle") + Label(translate("settings.ai.saved"), systemImage: "checkmark.circle") .foregroundColor(.green) .font(.caption) case .error(let message): @@ -108,11 +108,11 @@ struct AISettingsTab: View { // Info VStack(alignment: .leading, spacing: 6) { - Label("Analyzes code for security vulnerabilities", systemImage: "shield.lefthalf.filled") + Label(translate("settings.ai.info.analyzes"), systemImage: "shield.lefthalf.filled") .font(.caption) - Label("API key stored in macOS Keychain", systemImage: "lock.shield") + Label(translate("settings.ai.info.keychain"), systemImage: "lock.shield") .font(.caption) - Label("Uses your API credits", systemImage: "dollarsign.circle") + Label(translate("settings.ai.info.credits"), systemImage: "dollarsign.circle") .font(.caption) } .foregroundColor(.secondary) @@ -133,7 +133,7 @@ struct AISettingsTab: View { // Validate API key format guard apiKey.hasPrefix("sk-ant-") else { - saveStatus = .error("Invalid format") + saveStatus = .error(translate("settings.ai.error.invalidFormat")) isValidating = false return } @@ -150,7 +150,7 @@ struct AISettingsTab: View { apiKey = String(repeating: "*", count: 20) } } else { - saveStatus = .error("Save failed") + saveStatus = .error(translate("settings.ai.error.saveFailed")) } }