diff --git a/Easydict.xcodeproj/project.pbxproj b/Easydict.xcodeproj/project.pbxproj index d61b66620..9e8730358 100644 --- a/Easydict.xcodeproj/project.pbxproj +++ b/Easydict.xcodeproj/project.pbxproj @@ -259,7 +259,13 @@ 62E2BF4B2B4082BA00E42D38 /* AliResponse.swift in Sources */ = {isa = PBXBuildFile; fileRef = 62E2BF482B4082BA00E42D38 /* AliResponse.swift */; }; 62E2BF4C2B4082BA00E42D38 /* AliTranslateType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 62E2BF492B4082BA00E42D38 /* AliTranslateType.swift */; }; 62ED29A22B15F1F500901F51 /* EZWrapView.m in Sources */ = {isa = PBXBuildFile; fileRef = 62ED29A12B15F1F500901F51 /* EZWrapView.m */; }; + 960835502B6791F200C6A931 /* ShortcutValidator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9608354F2B6791F200C6A931 /* ShortcutValidator.swift */; }; + 96099AE22B5D40330055C4DD /* ShortcutTab.swift in Sources */ = {isa = PBXBuildFile; fileRef = 96099AE12B5D40330055C4DD /* ShortcutTab.swift */; }; + 9627F9382B59956800B1E999 /* GeneralShortcutSetting.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9627F9352B59956800B1E999 /* GeneralShortcutSetting.swift */; }; + 9627F9392B59956800B1E999 /* GeneralKeyHolderWrapper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9627F9362B59956800B1E999 /* GeneralKeyHolderWrapper.swift */; }; 9672D7D22B4008B40023B8FB /* MASShortcutBinder+EZMASShortcutBinder.m in Sources */ = {isa = PBXBuildFile; fileRef = 9672D7D12B4008B40023B8FB /* MASShortcutBinder+EZMASShortcutBinder.m */; }; + 967712EA2B5B913600105E0F /* KeyHolder in Frameworks */ = {isa = PBXBuildFile; productRef = 967712E92B5B913600105E0F /* KeyHolder */; }; + 967712EE2B5B943400105E0F /* Shortcut.swift in Sources */ = {isa = PBXBuildFile; fileRef = 967712ED2B5B943400105E0F /* Shortcut.swift */; }; A0B65CA0F31AC8ECFB8347CC /* Pods_EasydictTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 378E73A7EA8FC8FB9C975A63 /* Pods_EasydictTests.framework */; }; B87AC7E36367075BA5D13234 /* Pods_Easydict.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 6372B33DFF803C7096A82250 /* Pods_Easydict.framework */; }; C415C0AD2B450D4800A9D231 /* GeminiService.swift in Sources */ = {isa = PBXBuildFile; fileRef = C415C0AC2B450D4800A9D231 /* GeminiService.swift */; }; @@ -272,6 +278,8 @@ DC46DF802B4417B900DEAE3E /* Configuration.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC46DF7F2B4417B900DEAE3E /* Configuration.swift */; }; DC6D9C872B352EBC0055EFFC /* FontSizeHintView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC6D9C862B352EBC0055EFFC /* FontSizeHintView.swift */; }; DC6D9C892B3969510055EFFC /* Appearance.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC6D9C882B3969510055EFFC /* Appearance.swift */; }; + EA1013442B5DBDB1005E43F9 /* KeyCombo+Defaults.Serializable.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA1013432B5DBDB1005E43F9 /* KeyCombo+Defaults.Serializable.swift */; }; + EA3B81F92B5254AA004C0E8B /* Configuration.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA3B81F82B5254AA004C0E8B /* Configuration.swift */; }; DCF176F22B57CED700CA6026 /* Configuration+UserData.swift in Sources */ = {isa = PBXBuildFile; fileRef = DCF176F12B57CED700CA6026 /* Configuration+UserData.swift */; }; EA3B81F92B5254AA004C0E8B /* Configuration+Defaults.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA3B81F82B5254AA004C0E8B /* Configuration+Defaults.swift */; }; EA3B81FC2B52555C004C0E8B /* Defaults in Frameworks */ = {isa = PBXBuildFile; productRef = EA3B81FB2B52555C004C0E8B /* Defaults */; }; @@ -756,8 +764,13 @@ 62ED29A12B15F1F500901F51 /* EZWrapView.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = EZWrapView.m; sourceTree = ""; }; 6372B33DFF803C7096A82250 /* Pods_Easydict.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Easydict.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 91E3E579C6DB88658B4BB102 /* Pods-Easydict.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Easydict.release.xcconfig"; path = "Target Support Files/Pods-Easydict/Pods-Easydict.release.xcconfig"; sourceTree = ""; }; + 9608354F2B6791F200C6A931 /* ShortcutValidator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShortcutValidator.swift; sourceTree = ""; }; + 96099AE12B5D40330055C4DD /* ShortcutTab.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShortcutTab.swift; sourceTree = ""; }; + 9627F9352B59956800B1E999 /* GeneralShortcutSetting.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GeneralShortcutSetting.swift; sourceTree = ""; }; + 9627F9362B59956800B1E999 /* GeneralKeyHolderWrapper.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GeneralKeyHolderWrapper.swift; sourceTree = ""; }; 9672D7D02B4008B40023B8FB /* MASShortcutBinder+EZMASShortcutBinder.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "MASShortcutBinder+EZMASShortcutBinder.h"; sourceTree = ""; }; 9672D7D12B4008B40023B8FB /* MASShortcutBinder+EZMASShortcutBinder.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = "MASShortcutBinder+EZMASShortcutBinder.m"; sourceTree = ""; }; + 967712ED2B5B943400105E0F /* Shortcut.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Shortcut.swift; sourceTree = ""; }; A230E9A2358C7FBC7FB26189 /* Pods-EasydictTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-EasydictTests.debug.xcconfig"; path = "Target Support Files/Pods-EasydictTests/Pods-EasydictTests.debug.xcconfig"; sourceTree = ""; }; C415C0AC2B450D4800A9D231 /* GeminiService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GeminiService.swift; sourceTree = ""; }; C4DD01E82B12B3C80025EE8E /* TencentService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TencentService.swift; sourceTree = ""; }; @@ -771,6 +784,8 @@ DC46DF7F2B4417B900DEAE3E /* Configuration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Configuration.swift; sourceTree = ""; }; DC6D9C862B352EBC0055EFFC /* FontSizeHintView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FontSizeHintView.swift; sourceTree = ""; }; DC6D9C882B3969510055EFFC /* Appearance.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Appearance.swift; sourceTree = ""; }; + EA1013432B5DBDB1005E43F9 /* KeyCombo+Defaults.Serializable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "KeyCombo+Defaults.Serializable.swift"; sourceTree = ""; }; + EA3B81F82B5254AA004C0E8B /* Configuration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Configuration.swift; sourceTree = ""; }; DCF176F12B57CED700CA6026 /* Configuration+UserData.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Configuration+UserData.swift"; sourceTree = ""; }; EA3B81F82B5254AA004C0E8B /* Configuration+Defaults.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Configuration+Defaults.swift"; sourceTree = ""; }; EA9943E22B534C3300EE7B97 /* TTSServiceType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TTSServiceType.swift; sourceTree = ""; }; @@ -827,6 +842,7 @@ EA3B81FC2B52555C004C0E8B /* Defaults in Frameworks */, 03CF27FE2B3DA7D500E19B57 /* Realm in Frameworks */, 03A830902B4073E700112834 /* AppCenterAnalytics in Frameworks */, + 967712EA2B5B913600105E0F /* KeyHolder in Frameworks */, 03B63ABF2A86967800E155ED /* CoreServices.framework in Frameworks */, 038030972B4106800009230C /* CocoaLumberjackSwift in Frameworks */, 038EA1AA2B41169C008A6DD1 /* ZipArchive in Frameworks */, @@ -2079,6 +2095,7 @@ 27FE98032B3DCA9F000AD654 /* NewApp */ = { isa = PBXGroup; children = ( + 967712EB2B5B93E200105E0F /* Feature */, EA9943E12B534C2900EE7B97 /* Model */, EA9943DD2B534BAE00EE7B97 /* Utility */, EA3B81F72B52549B004C0E8B /* Configuration */, @@ -2112,12 +2129,14 @@ 27FE980C2B3DD749000AD654 /* Tabs */ = { isa = PBXGroup; children = ( + 9627F9332B59956800B1E999 /* View */, EAED41EA2B54A4900005FE0A /* ServiceConfiguration */, 278540332B3DE04F004E9488 /* GeneralTab.swift */, 0A057D6C2B499A000025C51D /* ServiceTab.swift */, 0A8685C72B552A590022534F /* DisabledAppTab.swift */, 276742042B3DC230002A2C75 /* PrivacyTab.swift */, 276742052B3DC230002A2C75 /* AboutTab.swift */, + 96099AE12B5D40330055C4DD /* ShortcutTab.swift */, 03832F532B5F6BE200D0DC64 /* AdvancedTab.swift */, ); path = Tabs; @@ -2170,6 +2189,40 @@ name = Frameworks; sourceTree = ""; }; + 9627F9332B59956800B1E999 /* View */ = { + isa = PBXGroup; + children = ( + 9627F9342B59956800B1E999 /* Shortcut */, + ); + path = View; + sourceTree = ""; + }; + 9627F9342B59956800B1E999 /* Shortcut */ = { + isa = PBXGroup; + children = ( + 9627F9352B59956800B1E999 /* GeneralShortcutSetting.swift */, + 9627F9362B59956800B1E999 /* GeneralKeyHolderWrapper.swift */, + ); + path = Shortcut; + sourceTree = ""; + }; + 967712EB2B5B93E200105E0F /* Feature */ = { + isa = PBXGroup; + children = ( + 967712EC2B5B941600105E0F /* Shortcut */, + ); + path = Feature; + sourceTree = ""; + }; + 967712EC2B5B941600105E0F /* Shortcut */ = { + isa = PBXGroup; + children = ( + 967712ED2B5B943400105E0F /* Shortcut.swift */, + 9608354F2B6791F200C6A931 /* ShortcutValidator.swift */, + ); + path = Shortcut; + sourceTree = ""; + }; 9CB57B9B45EC322A11ED8865 /* Pods */ = { isa = PBXGroup; children = ( @@ -2244,6 +2297,14 @@ path = ChangeFontSizeView; sourceTree = ""; }; + EA1013412B5DBDA5005E43F9 /* Defaults */ = { + isa = PBXGroup; + children = ( + EA1013432B5DBDB1005E43F9 /* KeyCombo+Defaults.Serializable.swift */, + ); + path = Defaults; + sourceTree = ""; + }; EA3B81F72B52549B004C0E8B /* Configuration */ = { isa = PBXGroup; children = ( @@ -2273,6 +2334,7 @@ EA9943E62B534D7C00EE7B97 /* Extensions */ = { isa = PBXGroup; children = ( + EA1013412B5DBDA5005E43F9 /* Defaults */, 038A723E2B62C07B004995E3 /* String */, EAED41F02B54B1A60005FE0A /* QueryService+ConfigurableService */, EA9943E72B534D8900EE7B97 /* LanguageDetectOptimizeExtensions.swift */, @@ -2399,6 +2461,7 @@ 038EA1A92B41169C008A6DD1 /* ZipArchive */, 038EA1AC2B41282F008A6DD1 /* MJExtension */, EA3B81FB2B52555C004C0E8B /* Defaults */, + 967712E92B5B913600105E0F /* KeyHolder */, 03022F182B3591AE00B63209 /* GoogleGenerativeAI */, ); productName = Bob; @@ -2459,6 +2522,7 @@ 038EA1A82B41169C008A6DD1 /* XCRemoteSwiftPackageReference "ZipArchive" */, 038EA1AB2B41282F008A6DD1 /* XCRemoteSwiftPackageReference "MJExtension" */, EA3B81FA2B52555C004C0E8B /* XCRemoteSwiftPackageReference "Defaults" */, + 967712E82B5B913600105E0F /* XCRemoteSwiftPackageReference "KeyHolder" */, 03022F172B3591AE00B63209 /* XCRemoteSwiftPackageReference "generative-ai-swift" */, ); productRefGroup = C99EEB192385796700FEE666 /* Products */; @@ -2675,6 +2739,7 @@ 62E2BF4A2B4082BA00E42D38 /* AliService.swift in Sources */, 03B0233729231FA6001C7E63 /* MMMake.m in Sources */, 03B0232E29231FA6001C7E63 /* MMCrashSignalExceptionHandler.m in Sources */, + 9627F9382B59956800B1E999 /* GeneralShortcutSetting.swift in Sources */, 03BDA7C42A26DA280079D04F /* NSDictionary+RubyDescription.m in Sources */, 62ED29A22B15F1F500901F51 /* EZWrapView.m in Sources */, C4DD01EB2B12BA250025EE8E /* TencentResponse.swift in Sources */, @@ -2699,6 +2764,7 @@ 03B3B8B22925D5B200168E8D /* EZPopButtonWindow.m in Sources */, 03B0231529231FA6001C7E63 /* SnipWindow.m in Sources */, 033363A0293A05D200FED9C8 /* EZSelectLanguageButton.m in Sources */, + 960835502B6791F200C6A931 /* ShortcutValidator.swift in Sources */, 03542A522937B69200C34C33 /* EZYoudaoTranslateResponse.m in Sources */, 03B0230129231FA6001C7E63 /* EZQueryView.m in Sources */, 03542A3D2937AF4F00C34C33 /* EZQueryResult.m in Sources */, @@ -2729,11 +2795,13 @@ 03882F8F29D95044005B5A52 /* CTScreen.m in Sources */, 27FE980B2B3DD5D1000AD654 /* MenuItemView.swift in Sources */, 03DC7C6A2A3CA852000BF7C9 /* EZAppCell.m in Sources */, + 96099AE22B5D40330055C4DD /* ShortcutTab.swift in Sources */, 0399C6AC29A860AA00B4AFCC /* EZOpenAIService.m in Sources */, 03542A432937B45E00C34C33 /* EZBaiduTranslate.m in Sources */, 03BB2DEB29F57DC000447EDD /* NSImage+EZSymbolmage.m in Sources */, 03B0230629231FA6001C7E63 /* EZLabel.m in Sources */, 03F25CB329327BC200E66A12 /* EZShortcut.m in Sources */, + EA1013442B5DBDB1005E43F9 /* KeyCombo+Defaults.Serializable.swift in Sources */, 033B7134293CE2430096E2DF /* EZWebViewTranslator.m in Sources */, 03CF88632B137F650030C199 /* Array+Convenience.swift in Sources */, 03B0231229231FA6001C7E63 /* NSObject+DarkMode.m in Sources */, @@ -2773,6 +2841,7 @@ 03542A342936F70F00C34C33 /* EZLanguageManager.m in Sources */, 6295DE312A84D82E006145F4 /* EZBingTranslateModel.m in Sources */, 0361967B2A0037F700806370 /* NSData+EZMD5.m in Sources */, + 967712EE2B5B943400105E0F /* Shortcut.swift in Sources */, 03BFFC68295F4B87004E033E /* EZYoudaoDictModel.m in Sources */, 03247E3A296AE8EC00AFCD67 /* EZLoadingAnimationView.m in Sources */, 0396D615292CC4C3006A11D9 /* EZLocalStorage.m in Sources */, @@ -2831,6 +2900,7 @@ 03B0233129231FA6001C7E63 /* MMCrash.m in Sources */, 03B0232629231FA6001C7E63 /* NSAttributedString+MM.m in Sources */, 03542A402937B3C900C34C33 /* EZOCRResult.m in Sources */, + 9627F9392B59956800B1E999 /* GeneralKeyHolderWrapper.swift in Sources */, C4DD01E92B12B3C80025EE8E /* TencentService.swift in Sources */, 0A2BA9602B49A989002872A4 /* Binding+DidSet.swift in Sources */, EA9943E32B534C3300EE7B97 /* TTSServiceType.swift in Sources */, @@ -3450,6 +3520,14 @@ minimumVersion = 5.8.1; }; }; + 967712E82B5B913600105E0F /* XCRemoteSwiftPackageReference "KeyHolder" */ = { + isa = XCRemoteSwiftPackageReference; + repositoryURL = "https://github.com/Clipy/KeyHolder.git"; + requirement = { + kind = upToNextMajorVersion; + minimumVersion = 4.2.0; + }; + }; EA3B81FA2B52555C004C0E8B /* XCRemoteSwiftPackageReference "Defaults" */ = { isa = XCRemoteSwiftPackageReference; repositoryURL = "https://github.com/sindresorhus/Defaults.git"; @@ -3541,6 +3619,11 @@ package = 2721E4CE2AFE920700A059AC /* XCRemoteSwiftPackageReference "Alamofire" */; productName = Alamofire; }; + 967712E92B5B913600105E0F /* KeyHolder */ = { + isa = XCSwiftPackageProductDependency; + package = 967712E82B5B913600105E0F /* XCRemoteSwiftPackageReference "KeyHolder" */; + productName = KeyHolder; + }; EA3B81FB2B52555C004C0E8B /* Defaults */ = { isa = XCSwiftPackageProductDependency; package = EA3B81FA2B52555C004C0E8B /* XCRemoteSwiftPackageReference "Defaults" */; diff --git a/Easydict.xcworkspace/xcshareddata/swiftpm/Package.resolved b/Easydict.xcworkspace/xcshareddata/swiftpm/Package.resolved index 3c8f58191..0d2cecd07 100644 --- a/Easydict.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/Easydict.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -144,6 +144,15 @@ "version" : "100.0.0" } }, + { + "identity" : "keyholder", + "kind" : "remoteSourceControl", + "location" : "https://github.com/Clipy/KeyHolder.git", + "state" : { + "revision" : "5da0ee06fd8709f7d812504bff1555462a3f6956", + "version" : "4.2.0" + } + }, { "identity" : "leveldb", "kind" : "remoteSourceControl", @@ -153,6 +162,15 @@ "version" : "1.22.3" } }, + { + "identity" : "magnet", + "kind" : "remoteSourceControl", + "location" : "https://github.com/Clipy/Magnet", + "state" : { + "revision" : "26d672a031fd33eac94887b4e19aca0ab4761f45", + "version" : "3.4.0" + } + }, { "identity" : "mjextension", "kind" : "remoteSourceControl", @@ -207,6 +225,15 @@ "version" : "10.45.2" } }, + { + "identity" : "sauce", + "kind" : "remoteSourceControl", + "location" : "https://github.com/Clipy/Sauce", + "state" : { + "revision" : "8f8fabaa8509c1a653d6c2c3c87396a4c493d876", + "version" : "2.4.0" + } + }, { "identity" : "snapkit", "kind" : "remoteSourceControl", diff --git a/Easydict/App/AppDelegate.m b/Easydict/App/AppDelegate.m index f4dc71d53..4d3f7c9ee 100644 --- a/Easydict/App/AppDelegate.m +++ b/Easydict/App/AppDelegate.m @@ -29,9 +29,10 @@ - (void)applicationDidFinishLaunching:(NSNotification *)aNotification { if (!EasydictNewAppManager.shared.enable) { [EZMenuItemManager.shared setup]; + [EZShortcut setup]; + } else { + [Shortcut setupShortcut]; } - - [EZShortcut setup]; [EZWindowManager.shared showMainWindowIfNedded]; diff --git a/Easydict/App/Easydict-Bridging-Header.h b/Easydict/App/Easydict-Bridging-Header.h index 7bcde5af5..66f840155 100644 --- a/Easydict/App/Easydict-Bridging-Header.h +++ b/Easydict/App/Easydict-Bridging-Header.h @@ -32,3 +32,5 @@ #import "DarkModeManager.h" #import "EZScriptExecutor.h" #import "EZOpenAIService.h" + + diff --git a/Easydict/App/Localizable.xcstrings b/Easydict/App/Localizable.xcstrings index 0819e750d..6e2c2cf10 100644 --- a/Easydict/App/Localizable.xcstrings +++ b/Easydict/App/Localizable.xcstrings @@ -46,6 +46,12 @@ }, "advanced" : { "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Advanced" + } + }, "zh-Hans" : { "stringUnit" : { "state" : "translated", @@ -1326,7 +1332,7 @@ }, "zh-Hans" : { "stringUnit" : { - "state" : "needs_review", + "state" : "translated", "value" : "Gemini 翻译" } } @@ -1342,6 +1348,23 @@ } } }, + "global_shortcut_setting" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Global Shortcut" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "全局快捷键" + } + } + } + }, "google_translate" : { "localizations" : { "en" : { @@ -1487,13 +1510,13 @@ "en" : { "stringUnit" : { "state" : "translated", - "value" : "Input Translate:" + "value" : "Input Translate" } }, "zh-Hans" : { "stringUnit" : { "state" : "translated", - "value" : "输入翻译:" + "value" : "输入翻译" } } } @@ -2329,13 +2352,13 @@ "en" : { "stringUnit" : { "state" : "translated", - "value" : "Select Text Translate:" + "value" : "Select Text Translate" } }, "zh-Hans" : { "stringUnit" : { "state" : "translated", - "value" : "划词翻译:" + "value" : "划词翻译" } } } @@ -3063,6 +3086,96 @@ } } }, + "shortcut" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Shortcut" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "快捷键" + } + } + } + }, + "shortcut_confict %@" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "%@" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "%@" + } + } + } + }, + "shortcut_confict_confirm" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Confirm" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "确定" + } + } + } + }, + "shortcut_confict_message (Shortcut.shared.confictMenuItem?.title ?? \"\")" : { + + }, + "shortcut_confict_message %@" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "The current shortcut can't be used because it is already used \"%@\"" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "当前快捷键无法使用,因为它已被\n\"%@\"占用" + } + } + } + }, + "shortcut_confict_title (keyCombo!.keyEquivalentModifierMaskString + keyCombo!.characters)" : { + + }, + "shortcut_confict_title %@" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Shortcut Confict With \"%@\" Unable Use This" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "快捷键\"%@\",无法使用" + } + } + } + }, "shortcut_select_translate_window_type" : { "localizations" : { "en" : { @@ -3164,13 +3277,13 @@ "en" : { "stringUnit" : { "state" : "translated", - "value" : "Show Mini Window:" + "value" : "Show Mini Window" } }, "zh-Hans" : { "stringUnit" : { "state" : "translated", - "value" : "显示迷你窗口:" + "value" : "显示迷你窗口" } } } @@ -3180,13 +3293,13 @@ "en" : { "stringUnit" : { "state" : "translated", - "value" : "Silent Screenshot OCR:" + "value" : "Silent Screenshot OCR" } }, "zh-Hans" : { "stringUnit" : { "state" : "translated", - "value" : "静默截图 OCR:" + "value" : "静默截图 OCR" } } } @@ -3229,13 +3342,13 @@ "en" : { "stringUnit" : { "state" : "translated", - "value" : "Snip Translate:" + "value" : "Snip Translate" } }, "zh-Hans" : { "stringUnit" : { "state" : "translated", - "value" : "截图翻译:" + "value" : "截图翻译" } } } diff --git a/Easydict/NewApp/Configuration/Configuration+Defaults.swift b/Easydict/NewApp/Configuration/Configuration+Defaults.swift index b8ab739dd..6b529ec6b 100644 --- a/Easydict/NewApp/Configuration/Configuration+Defaults.swift +++ b/Easydict/NewApp/Configuration/Configuration+Defaults.swift @@ -8,6 +8,7 @@ import Defaults import Foundation +import Magnet // Setting extension Defaults.Keys { @@ -162,3 +163,12 @@ extension Defaults.Keys { static let aliAccessKeyId = Key("EZAliAccessKeyId") static let aliAccessKeySecret = Key("EZAliAccessKeySecret") } + +/// shortcut +extension Defaults.Keys { + static let selectionShortcut = Key("EZSelectionShortcutKey_keyHolder", default: nil) + static let snipShortcut = Key("EZSnipShortcutKey_keyHolder", default: nil) + static let inputShortcut = Key("EZInputShortcutKey_keyHolder", default: nil) + static let screenshotOCRShortcut = Key("EZScreenshotOCRShortcutKey_keyHolder", default: nil) + static let showMiniWindowShortcut = Key("EZShowMiniShortcutKey_keyHolder", default: nil) +} diff --git a/Easydict/NewApp/Feature/Shortcut/Shortcut.swift b/Easydict/NewApp/Feature/Shortcut/Shortcut.swift new file mode 100644 index 000000000..434c33c77 --- /dev/null +++ b/Easydict/NewApp/Feature/Shortcut/Shortcut.swift @@ -0,0 +1,229 @@ +// +// Shortcut.swift +// Easydict +// +// Created by Sharker on 2024/1/20. +// Copyright © 2024 izual. All rights reserved. + +import Defaults +import Foundation +import KeyHolder +import Magnet +import SwiftUI + +/// Shortcut Service +public enum ShortcutType: String { + case inputTranslate = "EZInputShortcutKey" + case snipTranslate = "EZSnipShortcutKey" + case selectTranslate = "EZSelectionShortcutKey" + case silentScreenshotOcr = "EZScreenshotOCRShortcutKey" + case showMiniWindow = "EZShowMiniShortcutKey" +} + +// Confict Message +public struct ShortcutConfictAlertMessage: Identifiable { + public var id: String { message } + var title: String + var message: String +} + +class Shortcut: NSObject { + var confictMenuItem: NSMenuItem? + + static let shared = Shortcut() + + @objc static func setupShortcut() { + let shortcut = Shortcut.shared + shortcut.restoreShortcut() + } + + // Make sure the class has only one instance + // Should not init or copy outside + override private init() {} + + override func copy() -> Any { + self // SingletonClass.shared + } + + override func mutableCopy() -> Any { + self // SingletonClass.shared + } + + // Optional + func reset() { + // Reset all properties to default value + } +} + +// restore shortcut +extension Shortcut { + func restoreShortcut() { + // inputTranslate + bindingShortcut(keyCombo: Defaults[.inputShortcut], type: .inputTranslate) + // snipTranslate + bindingShortcut(keyCombo: Defaults[.snipShortcut], type: .snipTranslate) + // selectTranslate + bindingShortcut(keyCombo: Defaults[.selectionShortcut], type: .selectTranslate) + // silentScreenshotOcr + bindingShortcut(keyCombo: Defaults[.screenshotOCRShortcut], type: .silentScreenshotOcr) + // showMiniWindow + bindingShortcut(keyCombo: Defaults[.showMiniWindowShortcut], type: .showMiniWindow) + } +} + +// binding shortcut +extension Shortcut { + func bindingShortcut(keyCombo: KeyCombo?, type: ShortcutType) { + guard let keyCombo else { + HotKeyCenter.shared.unregisterHotKey(with: type.rawValue) + return + } + var hotKey: HotKey + switch type { + case .inputTranslate: + hotKey = HotKey(identifier: type.rawValue, + keyCombo: keyCombo, + target: Shortcut.shared, + action: #selector(Shortcut.inputTranslate)) + case .snipTranslate: + hotKey = HotKey(identifier: type.rawValue, + keyCombo: keyCombo, + target: Shortcut.shared, + action: #selector(Shortcut.snipTranslate)) + case .selectTranslate: + hotKey = HotKey(identifier: type.rawValue, + keyCombo: keyCombo, + target: Shortcut.shared, + action: #selector(Shortcut.selectTextTranslate)) + case .silentScreenshotOcr: + hotKey = HotKey(identifier: type.rawValue, + keyCombo: keyCombo, + target: Shortcut.shared, + action: #selector(Shortcut.screenshotOCR)) + case .showMiniWindow: + hotKey = HotKey(identifier: type.rawValue, + keyCombo: keyCombo, + target: Shortcut.shared, + action: #selector(Shortcut.showMiniFloatingWindow)) + } + hotKey.register() + } +} + +// shortcut binding func +extension Shortcut { + @objc func selectTextTranslate() { + EZWindowManager.shared().selectTextTranslate() + } + + @objc func snipTranslate() { + EZWindowManager.shared().snipTranslate() + } + + @objc func inputTranslate() { + EZWindowManager.shared().inputTranslate() + } + + @objc func showMiniFloatingWindow() { + EZWindowManager.shared().showMiniFloatingWindow() + } + + @objc func screenshotOCR() { + EZWindowManager.shared().screenshotOCR() + } +} + +// fetch shortcut KeyCombo +extension Shortcut { + public func shortcutKeyCombo(_ type: ShortcutType) -> KeyCombo? { + switch type { + case .inputTranslate: + guard let keyCombo = Defaults[.inputShortcut] else { return nil } + return keyCombo + case .snipTranslate: + guard let keyCombo = Defaults[.snipShortcut] else { return nil } + return keyCombo + case .selectTranslate: + guard let keyCombo = Defaults[.selectionShortcut] else { return nil } + return keyCombo + case .silentScreenshotOcr: + guard let keyCombo = Defaults[.screenshotOCRShortcut] else { return nil } + return keyCombo + case .showMiniWindow: + guard let keyCombo = Defaults[.showMiniWindowShortcut] else { return nil } + return keyCombo + } + } +} + +struct KeyboardShortcut: ViewModifier { + init(type: ShortcutType) { + let key: Defaults.Key = switch type { + case .inputTranslate: + .inputShortcut + case .snipTranslate: + .snipShortcut + case .selectTranslate: + .selectionShortcut + case .silentScreenshotOcr: + .screenshotOCRShortcut + case .showMiniWindow: + .showMiniWindowShortcut + } + + _shortcut = .init(key) + } + + @Default var shortcut: KeyCombo? + + func body(content: Content) -> some View { + if let shortcut { + content + .keyboardShortcut( + fetchShortcutKeyEquivalent(shortcut), + modifiers: fetchShortcutKeyEventModifiers(shortcut) + ) + } else { + content + } + } + + private func fetchShortcutKeyEquivalent(_ keyCombo: KeyCombo) -> KeyEquivalent { + if keyCombo.doubledModifiers { + return KeyEquivalent(Character(keyCombo.keyEquivalentModifierMaskString)) + } else { + return KeyEquivalent(Character(keyCombo.keyEquivalent)) + } + } + + private func fetchShortcutKeyEventModifiers(_ keyCombo: KeyCombo) -> EventModifiers { + var modifiers: EventModifiers = [] + + if keyCombo.keyEquivalentModifierMask.contains(NSEvent.ModifierFlags.command) { + modifiers.update(with: EventModifiers.command) + } + + if keyCombo.keyEquivalentModifierMask.contains(NSEvent.ModifierFlags.control) { + modifiers.update(with: EventModifiers.control) + } + + if keyCombo.keyEquivalentModifierMask.contains(NSEvent.ModifierFlags.option) { + modifiers.update(with: EventModifiers.option) + } + + if keyCombo.keyEquivalentModifierMask.contains(NSEvent.ModifierFlags.shift) { + modifiers.update(with: EventModifiers.shift) + } + + return modifiers + } +} + +/// can't using keyEquivalent and EventModifiers in SwiftUI MenuItemView direct, because item +/// keyboardShortcut not support double modifier key but can use ⌥ as character +public extension View { + @ViewBuilder + func keyboardShortcut(_ type: ShortcutType) -> some View { + modifier(KeyboardShortcut(type: type)) + } +} diff --git a/Easydict/NewApp/Feature/Shortcut/ShortcutValidator.swift b/Easydict/NewApp/Feature/Shortcut/ShortcutValidator.swift new file mode 100644 index 000000000..0f818a228 --- /dev/null +++ b/Easydict/NewApp/Feature/Shortcut/ShortcutValidator.swift @@ -0,0 +1,101 @@ +// +// ShortcutValidator.swift +// Easydict +// +// Created by Sharker on 2024/1/29. +// Copyright © 2024 izual. All rights reserved. +// + +import Carbon +import Carbon.HIToolbox +import Foundation +import KeyHolder +import Magnet +import Sauce + +extension Shortcut { + static func validateShortcut(_ keyCombo: KeyCombo) -> Bool { + validateShortcutConfictBySystem(keyCombo) || + validateShortcutConfictByMenuItem(keyCombo) || + validateShortcutConfictByCustom(keyCombo) + } +} + +// validate shortcut used by system +// ref: https://github.com/cocoabits/MASShortcut/blob/6f2603c6b6cc18f64a799e5d2c9d3bbc467c413a/Framework/Model/MASShortcutValidator.m#L94 +extension Shortcut { + static func validateShortcutConfictBySystem(_ keyCombo: KeyCombo) -> Bool { + systemUsedShortcut().contains(keyCombo) + } + + static func systemUsedShortcut() -> [KeyCombo] { + var shortcutsUnmanaged: Unmanaged? + guard + CopySymbolicHotKeys(&shortcutsUnmanaged) == noErr, + let shortcuts = shortcutsUnmanaged?.takeRetainedValue() as? [[String: Any]] + else { + assertionFailure("Could not get system keyboard shortcuts") + return [] + } + return shortcuts.compactMap { + guard + ($0[kHISymbolicHotKeyEnabled] as? Bool) == true, + let carbonKeyCode = $0[kHISymbolicHotKeyCode] as? Int, + let carbonModifiers = $0[kHISymbolicHotKeyModifiers] as? Int + else { + return nil + } + guard let key = Sauce.shared.key(for: Int(carbonKeyCode)) else { return nil } + guard let keyCombo = KeyCombo(key: key, carbonModifiers: carbonModifiers) else { return nil } + return keyCombo + } + } +} + +// validate shortcut used by menuItem +extension Shortcut { + static func validateShortcutConfictByMenuItem(_ keyCombo: KeyCombo) -> Bool { + if let item = menuItemUsedShortcut(keyCombo) { + Shortcut.shared.confictMenuItem = item + return true + } else { + return false + } + } + + static func menuItemUsedShortcut(_ keyCombo: KeyCombo) -> NSMenuItem? { + guard let mainMenu = NSApp.mainMenu else { + return nil + } + return menuItemWithMatchingShortcut(in: mainMenu, keyCombo: keyCombo) + } + + static func menuItemWithMatchingShortcut(in menu: NSMenu, keyCombo: KeyCombo) -> NSMenuItem? { + for item in menu.items { + let keyEquivalent = item.keyEquivalent + let keyEquivalentModifierMask = item.keyEquivalentModifierMask + if keyCombo.keyEquivalent == keyEquivalent, + keyCombo.keyEquivalentModifierMask == keyEquivalentModifierMask, + keyCombo.keyEquivalent != "" + { + return item + } + if let submenu = item.submenu, + let menuItem = menuItemWithMatchingShortcut(in: submenu, keyCombo: keyCombo) + { + return menuItem + } + } + return nil + } +} + +// validate shortcut used by custom +// ref: https://support.apple.com/zh-cn/HT201236 +extension Shortcut { + static func validateShortcutConfictByCustom(_: KeyCombo) -> Bool { + false + } + + static func customUsedShortcut(_: KeyCombo) {} +} diff --git a/Easydict/NewApp/Utility/Extensions/Defaults/KeyCombo+Defaults.Serializable.swift b/Easydict/NewApp/Utility/Extensions/Defaults/KeyCombo+Defaults.Serializable.swift new file mode 100644 index 000000000..6858aa387 --- /dev/null +++ b/Easydict/NewApp/Utility/Extensions/Defaults/KeyCombo+Defaults.Serializable.swift @@ -0,0 +1,31 @@ +// +// KeyCombo+Defaults.Serializable.swift +// Easydict +// +// Created by 戴藏龙 on 2024/1/21. +// Copyright © 2024 izual. All rights reserved. +// + +import Defaults +import Foundation +import Magnet + +extension KeyCombo: Defaults.Serializable { + public static var bridge = ShortcutBridge() + + public struct ShortcutBridge: Defaults.Bridge { + public func serialize(_ value: Magnet.KeyCombo??) -> Data? { + guard let value else { return nil } + return try? JSONEncoder().encode(value) + } + + public func deserialize(_ object: Data?) -> Magnet.KeyCombo?? { + guard let data = object else { return nil } + return try? JSONDecoder().decode(KeyCombo.self, from: data) as Magnet.KeyCombo? + } + + public typealias Value = KeyCombo? + + public typealias Serializable = Data + } +} diff --git a/Easydict/NewApp/View/MenuItemView.swift b/Easydict/NewApp/View/MenuItemView.swift index 7b25db502..2c5a6aca4 100644 --- a/Easydict/NewApp/View/MenuItemView.swift +++ b/Easydict/NewApp/View/MenuItemView.swift @@ -32,11 +32,16 @@ struct MenuItemView: View { versionItem Divider() inputItem + .keyboardShortcut(.inputTranslate) screenshotItem + .keyboardShortcut(.snipTranslate) selectWordItem + .keyboardShortcut(.selectTranslate) miniWindowItem + .keyboardShortcut(.showMiniWindow) Divider() ocrItem + .keyboardShortcut(.silentScreenshotOcr) Divider() settingItem .keyboardShortcut(.init(",")) diff --git a/Easydict/NewApp/View/SettingView/SettingView.swift b/Easydict/NewApp/View/SettingView/SettingView.swift index 81333b8ee..bb631a6e0 100644 --- a/Easydict/NewApp/View/SettingView/SettingView.swift +++ b/Easydict/NewApp/View/SettingView/SettingView.swift @@ -13,6 +13,7 @@ enum SettingTab: Int { case service case disabled case advanced + case shortcut case privacy case about } @@ -39,6 +40,9 @@ struct SettingView: View { .tabItem { Label("advanced", systemImage: "gearshape.2") } .tag(SettingTab.advanced) + ShortcutTab() + .tabItem { Label("shortcut", systemImage: "command.square") } + .tag(SettingTab.shortcut) PrivacyTab() .tabItem { Label("privacy", systemImage: "hand.raised.square") } .tag(SettingTab.privacy) @@ -69,7 +73,7 @@ struct SettingView: View { let height = switch selection { case .general: maxWidth - case .service, .disabled: + case .service, .disabled, .shortcut: 500 case .privacy: 320 diff --git a/Easydict/NewApp/View/SettingView/Tabs/ShortcutTab.swift b/Easydict/NewApp/View/SettingView/Tabs/ShortcutTab.swift new file mode 100644 index 000000000..52daaabe6 --- /dev/null +++ b/Easydict/NewApp/View/SettingView/Tabs/ShortcutTab.swift @@ -0,0 +1,25 @@ +// +// ShortcutTab.swift +// Easydict +// +// Created by Sharker on 2024/1/21. +// Copyright © 2024 izual. All rights reserved. +// + +import SwiftUI + +@available(macOS 13, *) +struct ShortcutTab: View { + var body: some View { + Form { + // Global shortcut + GeneralShortcutSettingView() + // In app shortcut + }.formStyle(.grouped) + } +} + +@available(macOS 13, *) +#Preview { + ShortcutTab() +} diff --git a/Easydict/NewApp/View/SettingView/Tabs/View/Shortcut/GeneralKeyHolderWrapper.swift b/Easydict/NewApp/View/SettingView/Tabs/View/Shortcut/GeneralKeyHolderWrapper.swift new file mode 100644 index 000000000..e94dfda4e --- /dev/null +++ b/Easydict/NewApp/View/SettingView/Tabs/View/Shortcut/GeneralKeyHolderWrapper.swift @@ -0,0 +1,115 @@ +// +// GeneralKeyHolderWrapper.swift +// Easydict +// +// Created by Sharker on 2024/1/2. +// Copyright © 2024 izual. All rights reserved. +// + +import AppKit +import Defaults +import KeyHolder +import Magnet +import SwiftUI + +struct GeneralKeyHolderWrapper: NSViewRepresentable { + func makeCoordinator() -> Coordinator { + .init(shortcutType: type, confictAlterMessage: $confictAlterMessage) + } + + private var type: ShortcutType + @Binding var confictAlterMessage: ShortcutConfictAlertMessage + init(shortcutType: ShortcutType, confictAlterMessage: Binding) { + type = shortcutType + _confictAlterMessage = confictAlterMessage + } + + func makeNSView(context: Context) -> some NSView { + let recordView = RecordView(frame: CGRect.zero) + recordView.tintColor = NSColor(red: 0.164, green: 0.517, blue: 0.823, alpha: 1) + recordView.delegate = context.coordinator + recordView.layer?.cornerRadius = 6.0 + recordView.layer?.masksToBounds = true + recordView.clearButtonMode = .whenRecorded + + context.coordinator.restoreKeyCombo(recordView) + return recordView + } + + func updateNSView(_: NSViewType, context _: Context) {} +} + +extension GeneralKeyHolderWrapper { + class Coordinator: NSObject, RecordViewDelegate { + private var type: ShortcutType + @Binding var confictAlterMessage: ShortcutConfictAlertMessage + init(shortcutType: ShortcutType, confictAlterMessage: Binding) { + type = shortcutType + _confictAlterMessage = confictAlterMessage + } + + func recordViewShouldBeginRecording(_: KeyHolder.RecordView) -> Bool { + true + } + + func recordView(_: KeyHolder.RecordView, canRecordKeyCombo _: Magnet.KeyCombo) -> Bool { + true + } + + func recordViewDidEndRecording(_: RecordView) {} + + func recordView(_ recordView: RecordView, didChangeKeyCombo keyCombo: KeyCombo?) { + if let key = keyCombo { + // shortcut validate confict + if Shortcut.validateShortcut(key) { + if #available(macOS 12, *) { + confictAlterMessage = ShortcutConfictAlertMessage(title: String(localized: "shortcut_confict_title \(keyCombo!.keyEquivalentModifierMaskString + keyCombo!.characters)"), message: String(localized: "shortcut_confict_message \(Shortcut.shared.confictMenuItem?.title ?? "")")) + } else { + // Fallback on earlier versions + let title = NSLocalizedString("shortcut_confict_title \(keyCombo!.keyEquivalentModifierMaskString + keyCombo!.characters)", comment: "") + let msg = NSLocalizedString("shortcut_confict_message \(Shortcut.shared.confictMenuItem?.title ?? "")", comment: "") + confictAlterMessage = ShortcutConfictAlertMessage(title: title, message: msg) + } + recordView.clear() + return + } + } + storeKeyCombo(with: keyCombo) + Shortcut.shared.bindingShortcut(keyCombo: keyCombo, type: type) + } + + func restoreKeyCombo(_ recordView: RecordView) { + var keyCombo: KeyCombo? + switch type { + case .inputTranslate: + keyCombo = Defaults[.inputShortcut] + case .snipTranslate: + keyCombo = Defaults[.snipShortcut] + case .selectTranslate: + keyCombo = Defaults[.selectionShortcut] + case .silentScreenshotOcr: + keyCombo = Defaults[.screenshotOCRShortcut] + case .showMiniWindow: + keyCombo = Defaults[.showMiniWindowShortcut] + } + recordView.keyCombo = keyCombo + Shortcut.shared.bindingShortcut(keyCombo: keyCombo, type: type) + } + + // shortcut + func storeKeyCombo(with keyCombo: KeyCombo?) { + switch type { + case .inputTranslate: + Defaults[.inputShortcut] = keyCombo + case .snipTranslate: + Defaults[.snipShortcut] = keyCombo + case .selectTranslate: + Defaults[.selectionShortcut] = keyCombo + case .silentScreenshotOcr: + Defaults[.screenshotOCRShortcut] = keyCombo + case .showMiniWindow: + Defaults[.showMiniWindowShortcut] = keyCombo + } + } + } +} diff --git a/Easydict/NewApp/View/SettingView/Tabs/View/Shortcut/GeneralShortcutSetting.swift b/Easydict/NewApp/View/SettingView/Tabs/View/Shortcut/GeneralShortcutSetting.swift new file mode 100644 index 000000000..7387a5b32 --- /dev/null +++ b/Easydict/NewApp/View/SettingView/Tabs/View/Shortcut/GeneralShortcutSetting.swift @@ -0,0 +1,69 @@ +// +// GeneralShortcutSetting.swift +// Easydict +// +// Created by Sharker on 2024/1/1. +// Copyright © 2024 izual. All rights reserved. +// + +import SwiftUI + +@available(macOS 13, *) +extension ShortcutTab { + struct GeneralShortcutSettingView: View { + @State var confictAlterMessage: ShortcutConfictAlertMessage = .init(title: "", message: "") + + var body: some View { + let showAlter = Binding( + get: { + if !confictAlterMessage.message.isEmpty { + true + } else { + false + } + }, set: { _ in + } + ) + Section { + HStack { + Text("input_translate") + Spacer() + GeneralKeyHolderWrapper(shortcutType: .inputTranslate, confictAlterMessage: $confictAlterMessage).frame(width: 180, height: 24) + } + HStack { + Text("snip_translate") + Spacer() + GeneralKeyHolderWrapper(shortcutType: .snipTranslate, confictAlterMessage: $confictAlterMessage).frame(width: 180, height: 24) + } + HStack { + Text("select_translate") + Spacer() + GeneralKeyHolderWrapper(shortcutType: .selectTranslate, confictAlterMessage: $confictAlterMessage).frame(width: 180, height: 24) + } + HStack { + Text("show_mini_window") + Spacer() + GeneralKeyHolderWrapper(shortcutType: .showMiniWindow, confictAlterMessage: $confictAlterMessage).frame(width: 180, height: 24) + } + HStack { + Text("silent_screenshot_ocr") + Spacer() + GeneralKeyHolderWrapper(shortcutType: .silentScreenshotOcr, confictAlterMessage: $confictAlterMessage).frame(width: 180, height: 24) + } + } header: { + Text("global_shortcut_setting") + } + + .alert(String(localized: "shortcut_confict \(confictAlterMessage.title)"), + isPresented: showAlter, + presenting: confictAlterMessage) + { _ in + Button(String(localized: "shortcut_confict_confirm")) { + confictAlterMessage = ShortcutConfictAlertMessage(title: "", message: "") + } + } message: { message in + Text(message.message) + } + } + } +}