From 35222c2e150bebe2fb200b7c0fbb5236e8229cb5 Mon Sep 17 00:00:00 2001 From: Lava <34743145+CanglongCl@users.noreply.github.com> Date: Sun, 14 Jan 2024 01:25:22 -0800 Subject: [PATCH] Introduce `Defaults` package into project and implement Setting - General with SwiftUI (#323) * import Defaults package * refactor EZConfiguration using Defaults package and Implement Boolean options in SwiftUI Setting View * complete enum options in `GeneralTab` of `Setting` * Font Size and Appearance setting * rename EZConfiguration file in swift to Configuration * rename `TTSService` with `TTSServiceType` * renaming TTSService to TTSServiceType and remove EZ prefix of some file * maintain the order of languages * filter classical Chinese in first/second language options * `Defaults ` package use version instead of branch --- Easydict.xcodeproj/project.pbxproj | 73 +++ .../xcshareddata/swiftpm/Package.resolved | 9 + Easydict/App/Localizable.xcstrings | 532 +++++++++++++++++- .../Feature/Configuration/Appearance.swift | 3 +- .../Feature/Configuration/EZConfiguration.h | 1 + .../Tencent/TencentTranslateType.swift | 14 - .../NewApp/Configuration/Configuration.swift | 55 ++ Easydict/NewApp/Model/TTSServiceType.swift | 80 +++ .../LanguageDetectOptimizeExtensions.swift | 32 ++ .../Extensions/LanguageExtensions.swift | 78 +++ .../ShowWindowPositionExtensions.swift | 34 ++ .../Extensions/WindowTypeExtensions.swift | 34 ++ .../View/SettingView/Tabs/GeneralTab.swift | 268 ++++++++- 13 files changed, 1176 insertions(+), 37 deletions(-) create mode 100644 Easydict/NewApp/Configuration/Configuration.swift create mode 100644 Easydict/NewApp/Model/TTSServiceType.swift create mode 100644 Easydict/NewApp/Utility/Extensions/LanguageDetectOptimizeExtensions.swift create mode 100644 Easydict/NewApp/Utility/Extensions/LanguageExtensions.swift create mode 100644 Easydict/NewApp/Utility/Extensions/ShowWindowPositionExtensions.swift create mode 100644 Easydict/NewApp/Utility/Extensions/WindowTypeExtensions.swift diff --git a/Easydict.xcodeproj/project.pbxproj b/Easydict.xcodeproj/project.pbxproj index 56aeb6d3f..ec44cd1ab 100644 --- a/Easydict.xcodeproj/project.pbxproj +++ b/Easydict.xcodeproj/project.pbxproj @@ -260,6 +260,13 @@ DC3C643F2B187119008EEDD8 /* ChangeFontSizeView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC3C643E2B187119008EEDD8 /* ChangeFontSizeView.swift */; }; DC6D9C872B352EBC0055EFFC /* FontSizeHintView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC6D9C862B352EBC0055EFFC /* FontSizeHintView.swift */; }; DC6D9C892B3969510055EFFC /* Appearance.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC6D9C882B3969510055EFFC /* Appearance.swift */; }; + EA3B81F92B5254AA004C0E8B /* Configuration.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA3B81F82B5254AA004C0E8B /* Configuration.swift */; }; + EA3B81FC2B52555C004C0E8B /* Defaults in Frameworks */ = {isa = PBXBuildFile; productRef = EA3B81FB2B52555C004C0E8B /* Defaults */; }; + EA9943E32B534C3300EE7B97 /* TTSServiceType.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA9943E22B534C3300EE7B97 /* TTSServiceType.swift */; }; + EA9943E82B534D8900EE7B97 /* LanguageDetectOptimizeExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA9943E72B534D8900EE7B97 /* LanguageDetectOptimizeExtensions.swift */; }; + EA9943EE2B5353AB00EE7B97 /* WindowTypeExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA9943ED2B5353AB00EE7B97 /* WindowTypeExtensions.swift */; }; + EA9943F02B5354C400EE7B97 /* ShowWindowPositionExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA9943EF2B5354C400EE7B97 /* ShowWindowPositionExtensions.swift */; }; + EA9943F22B5358BF00EE7B97 /* LanguageExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA9943F12B5358BF00EE7B97 /* LanguageExtensions.swift */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -737,6 +744,12 @@ DC3C643E2B187119008EEDD8 /* ChangeFontSizeView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ChangeFontSizeView.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 = ""; }; + EA3B81F82B5254AA004C0E8B /* Configuration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Configuration.swift; sourceTree = ""; }; + EA9943E22B534C3300EE7B97 /* TTSServiceType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TTSServiceType.swift; sourceTree = ""; }; + EA9943E72B534D8900EE7B97 /* LanguageDetectOptimizeExtensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LanguageDetectOptimizeExtensions.swift; sourceTree = ""; }; + EA9943ED2B5353AB00EE7B97 /* WindowTypeExtensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WindowTypeExtensions.swift; sourceTree = ""; }; + EA9943EF2B5354C400EE7B97 /* ShowWindowPositionExtensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShowWindowPositionExtensions.swift; sourceTree = ""; }; + EA9943F12B5358BF00EE7B97 /* LanguageExtensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LanguageExtensions.swift; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -778,6 +791,7 @@ 03A830922B4073E700112834 /* AppCenterCrashes in Frameworks */, 03022F1C2B35DEBA00B63209 /* Hue in Frameworks */, 03A830952B4076FC00112834 /* FirebaseAnalyticsSwift in Frameworks */, + EA3B81FC2B52555C004C0E8B /* Defaults in Frameworks */, 03CF27FE2B3DA7D500E19B57 /* Realm in Frameworks */, 03A830902B4073E700112834 /* AppCenterAnalytics in Frameworks */, 03B63ABF2A86967800E155ED /* CoreServices.framework in Frameworks */, @@ -1994,6 +2008,9 @@ 27FE98032B3DCA9F000AD654 /* NewApp */ = { isa = PBXGroup; children = ( + EA9943E12B534C2900EE7B97 /* Model */, + EA9943DD2B534BAE00EE7B97 /* Utility */, + EA3B81F72B52549B004C0E8B /* Configuration */, 27FE95262B3DC55F000AD654 /* EasydictApp.swift */, 27FE98042B3DCB09000AD654 /* NewAppManager.swift */, 27FE98062B3DD525000AD654 /* View */, @@ -2143,6 +2160,41 @@ path = ChangeFontSizeView; sourceTree = ""; }; + EA3B81F72B52549B004C0E8B /* Configuration */ = { + isa = PBXGroup; + children = ( + EA3B81F82B5254AA004C0E8B /* Configuration.swift */, + ); + path = Configuration; + sourceTree = ""; + }; + EA9943DD2B534BAE00EE7B97 /* Utility */ = { + isa = PBXGroup; + children = ( + EA9943E62B534D7C00EE7B97 /* Extensions */, + ); + path = Utility; + sourceTree = ""; + }; + EA9943E12B534C2900EE7B97 /* Model */ = { + isa = PBXGroup; + children = ( + EA9943E22B534C3300EE7B97 /* TTSServiceType.swift */, + ); + path = Model; + sourceTree = ""; + }; + EA9943E62B534D7C00EE7B97 /* Extensions */ = { + isa = PBXGroup; + children = ( + EA9943E72B534D8900EE7B97 /* LanguageDetectOptimizeExtensions.swift */, + EA9943ED2B5353AB00EE7B97 /* WindowTypeExtensions.swift */, + EA9943EF2B5354C400EE7B97 /* ShowWindowPositionExtensions.swift */, + EA9943F12B5358BF00EE7B97 /* LanguageExtensions.swift */, + ); + path = Extensions; + sourceTree = ""; + }; /* End PBXGroup section */ /* Begin PBXNativeTarget section */ @@ -2234,6 +2286,7 @@ 038030962B4106800009230C /* CocoaLumberjackSwift */, 038EA1A92B41169C008A6DD1 /* ZipArchive */, 038EA1AC2B41282F008A6DD1 /* MJExtension */, + EA3B81FB2B52555C004C0E8B /* Defaults */, ); productName = Bob; productReference = C99EEB182385796700FEE666 /* Easydict-debug.app */; @@ -2292,6 +2345,7 @@ 038030932B4106800009230C /* XCRemoteSwiftPackageReference "CocoaLumberjack" */, 038EA1A82B41169C008A6DD1 /* XCRemoteSwiftPackageReference "ZipArchive" */, 038EA1AB2B41282F008A6DD1 /* XCRemoteSwiftPackageReference "MJExtension" */, + EA3B81FA2B52555C004C0E8B /* XCRemoteSwiftPackageReference "Defaults" */, ); productRefGroup = C99EEB192385796700FEE666 /* Products */; projectDirPath = ""; @@ -2493,6 +2547,7 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + EA9943EE2B5353AB00EE7B97 /* WindowTypeExtensions.swift in Sources */, 030570E22ADB919900C9905E /* EZAppleScriptManager.m in Sources */, 03991158292927E000E1B06D /* EZTitlebar.m in Sources */, 03D8A65C2A433B4100D9A968 /* EZConfiguration+EZUserData.m in Sources */, @@ -2591,6 +2646,7 @@ 03B0233229231FA6001C7E63 /* MMLog.swift in Sources */, 03DC7C5E2A3ABE28000BF7C9 /* EZConstKey.m in Sources */, 62E2BF4C2B4082BA00E42D38 /* AliTranslateType.swift in Sources */, + EA3B81F92B5254AA004C0E8B /* Configuration.swift in Sources */, 03E3E7C22ADE318800812C84 /* EZQueryMenuTextView.m in Sources */, 03B0231829231FA6001C7E63 /* SnipWindowController.m in Sources */, 03542A342936F70F00C34C33 /* EZLanguageManager.m in Sources */, @@ -2627,6 +2683,7 @@ 0333FDA62A035D5700891515 /* NSString+EZChineseText.m in Sources */, 03E02A222924E77100A10260 /* EZMenuItemManager.m in Sources */, 039D119929D5E26300C93F46 /* EZAudioUtils.m in Sources */, + EA9943F22B5358BF00EE7B97 /* LanguageExtensions.swift in Sources */, 03B0231729231FA6001C7E63 /* Snip.m in Sources */, 03BFFC6E295FE59C004E033E /* EZQueryResult+EZYoudaoDictModel.m in Sources */, DC3C643F2B187119008EEDD8 /* ChangeFontSizeView.swift in Sources */, @@ -2644,10 +2701,12 @@ 0320C5872B29F35700861B3D /* QueryServiceRecord.swift in Sources */, 03FC699A2B39D13A0035D2DA /* EZOpenAIChatResponse.m in Sources */, 03B022FA29231FA6001C7E63 /* EZServiceTypes.m in Sources */, + EA9943F02B5354C400EE7B97 /* ShowWindowPositionExtensions.swift in Sources */, 03B0233129231FA6001C7E63 /* MMCrash.m in Sources */, 03B0232629231FA6001C7E63 /* NSAttributedString+MM.m in Sources */, 03542A402937B3C900C34C33 /* EZOCRResult.m in Sources */, C4DD01E92B12B3C80025EE8E /* TencentService.swift in Sources */, + EA9943E32B534C3300EE7B97 /* TTSServiceType.swift in Sources */, 036A0DBB2AD941F9006E6D4F /* EZReplaceTextButton.m in Sources */, 03DC7C662A3CA465000BF7C9 /* HWSegmentedControl.m in Sources */, 037E006D2B3DC098006491C6 /* EZOpenAIService+EZPromptMessages.m in Sources */, @@ -2683,6 +2742,7 @@ DC6D9C872B352EBC0055EFFC /* FontSizeHintView.swift in Sources */, 03B0232429231FA6001C7E63 /* NSUserDefaults+MM.m in Sources */, 03542A5E2938F05B00C34C33 /* EZLanguageModel.m in Sources */, + EA9943E82B534D8900EE7B97 /* LanguageDetectOptimizeExtensions.swift in Sources */, 03F639952AA6CFBB009B9914 /* EZBingConfig.m in Sources */, 03D2A3E329F4C6F50035CED4 /* EZNetworkManager.m in Sources */, 0309E1ED292B439A00AFB76A /* EZTextView.m in Sources */, @@ -3251,6 +3311,14 @@ minimumVersion = 5.8.1; }; }; + EA3B81FA2B52555C004C0E8B /* XCRemoteSwiftPackageReference "Defaults" */ = { + isa = XCRemoteSwiftPackageReference; + repositoryURL = "https://github.com/sindresorhus/Defaults.git"; + requirement = { + kind = upToNextMajorVersion; + minimumVersion = 7.3.1; + }; + }; /* End XCRemoteSwiftPackageReference section */ /* Begin XCSwiftPackageProductDependency section */ @@ -3329,6 +3397,11 @@ package = 2721E4CE2AFE920700A059AC /* XCRemoteSwiftPackageReference "Alamofire" */; productName = Alamofire; }; + EA3B81FB2B52555C004C0E8B /* Defaults */ = { + isa = XCSwiftPackageProductDependency; + package = EA3B81FA2B52555C004C0E8B /* XCRemoteSwiftPackageReference "Defaults" */; + productName = Defaults; + }; /* End XCSwiftPackageProductDependency section */ }; rootObject = C99EEB102385796700FEE666 /* Project object */; diff --git a/Easydict.xcworkspace/xcshareddata/swiftpm/Package.resolved b/Easydict.xcworkspace/xcshareddata/swiftpm/Package.resolved index bc7e5cc78..9c63ea7f5 100644 --- a/Easydict.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/Easydict.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -54,6 +54,15 @@ "version" : "1.8.0" } }, + { + "identity" : "defaults", + "kind" : "remoteSourceControl", + "location" : "https://github.com/sindresorhus/Defaults.git", + "state" : { + "revision" : "3efef5a28ebdbbe922d4a2049493733ed14475a6", + "version" : "7.3.1" + } + }, { "identity" : "firebase-ios-sdk", "kind" : "remoteSourceControl", diff --git a/Easydict/App/Localizable.xcstrings b/Easydict/App/Localizable.xcstrings index 160e6409b..aa35e533b 100644 --- a/Easydict/App/Localizable.xcstrings +++ b/Easydict/App/Localizable.xcstrings @@ -845,7 +845,7 @@ "zh-Hans" : { "stringUnit" : { "state" : "translated", - "value" : "划词内容为空时生效" + "value" : "划词内容为空时禁用复制提示音" } } } @@ -907,7 +907,7 @@ "localizations" : { "zh-Hans" : { "stringUnit" : { - "state" : "translated", + "state" : "needs_review", "value" : "以 SwiftUI App模式启动,重启App生效" } } @@ -1675,6 +1675,9 @@ } } } + }, + "none_window" : { + }, "ocr_result_is_empty" : { "localizations" : { @@ -2173,6 +2176,528 @@ } } }, + "setting.general.advance.default_tts_service" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Default TTS Service" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "默认 TTS 服务" + } + } + } + }, + "setting.general.advance.enable_beta_feature" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Enable Beta features" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "启用 Beta 特性" + } + } + } + }, + "setting.general.advance.header" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Advance" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "高级" + } + } + } + }, + "setting.general.appearance.header" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Appearance" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "外观" + } + } + } + }, + "setting.general.appearance.light_dark_appearance" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Appearance" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "外观" + } + } + } + }, + "setting.general.auto_copy.header" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Auto Copy" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "自动复制" + } + } + } + }, + "setting.general.auto_query.header" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Auto Query" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "自动查询" + } + } + } + }, + "setting.general.font.font_size.label" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Font Size" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "字体大小" + } + } + } + }, + "setting.general.font.header" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Font" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "字体" + } + } + } + }, + "setting.general.input.header" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Input Field" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "输入框" + } + } + } + }, + "setting.general.language.duplicated_alert %@%@%@" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "The first language should not be the same as the second language (%1$@). The %2$@ was replaced with %3$@." + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "第一语言不能与第二语言相同(%1$@)。%2$@已被替换为%3$@。" + } + } + } + }, + "setting.general.language.duplicated_alert.field.first" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "first language" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "第二语言" + } + } + } + }, + "setting.general.language.duplicated_alert.field.second" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "second language" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "第一语言" + } + } + } + }, + "setting.general.language.duplicated_alert.title" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Language Duplicated" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "语言重复" + } + } + } + }, + "setting.general.language.first_language" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "First Language" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "第一语言" + } + } + } + }, + "setting.general.language.header" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Language" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "语言" + } + } + } + }, + "setting.general.language.language_detect_optimize" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Language Detection" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "语种识别" + } + } + } + }, + "setting.general.language.second_language" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Second Language" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "第二语言" + } + } + } + }, + "setting.general.mouse_query.adjust_pop_button_origin" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Adjust Query Icon Position to avoid conflict with PopClip" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "needs_review", + "value" : "调整查询图标位置(避免和 PopClip 显示冲突)" + } + } + } + }, + "setting.general.mouse_query.header" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Query with Mouse" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "鼠标查询" + } + } + } + }, + "setting.general.quick_link.header" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Quick Link" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "快捷功能" + } + } + } + }, + "setting.general.voice.auto_play_word_audio" : { + "localizations" : { + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "查询英语单词后自动播放发音" + } + } + } + }, + "setting.general.voice.disable_empty_copy_beep_msg" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Disable 'beep' when selected text is empty" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "划词内容为空时" + } + } + } + }, + "setting.general.voice.header" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Sound" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "声音" + } + } + } + }, + "setting.general.window.fixed_window_position" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Floating Window Position" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "侧悬浮窗口位置" + } + } + } + }, + "setting.general.window.mouse_select_translate_window_type" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Mouse Window Type" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "鼠标划词窗口类型" + } + } + } + }, + "setting.general.window.shortcut_select_translate_window_type" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Shortcut Window Type" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "快捷键划词窗口类型" + } + } + } + }, + "setting.general.windows.header" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Window" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "窗口" + } + } + } + }, + "setting.tts_service.options.apple" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Apple" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "苹果" + } + } + } + }, + "setting.tts_service.options.baidu" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Baidu" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "百度" + } + } + } + }, + "setting.tts_service.options.bing" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Bing" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "Bing" + } + } + } + }, + "setting.tts_service.options.google" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Google" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "Google" + } + } + } + }, + "setting.tts_service.options.youdao" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Youdao" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "有道" + } + } + } + }, "Settings..." : { "localizations" : { "zh-Hans" : { @@ -2516,6 +3041,9 @@ } } } + }, + "unknown_option" : { + }, "unpin" : { "localizations" : { diff --git a/Easydict/Feature/Configuration/Appearance.swift b/Easydict/Feature/Configuration/Appearance.swift index 3a59f1124..eb49593cc 100644 --- a/Easydict/Feature/Configuration/Appearance.swift +++ b/Easydict/Feature/Configuration/Appearance.swift @@ -6,9 +6,10 @@ // Copyright © 2023 izual. All rights reserved. // +import Defaults import Foundation -@objc enum AppearenceType: Int, CaseIterable { +@objc enum AppearenceType: Int, CaseIterable, Defaults.Serializable { case followSystem = 0 case light case dark diff --git a/Easydict/Feature/Configuration/EZConfiguration.h b/Easydict/Feature/Configuration/EZConfiguration.h index bc07911ee..310f47031 100644 --- a/Easydict/Feature/Configuration/EZConfiguration.h +++ b/Easydict/Feature/Configuration/EZConfiguration.h @@ -23,6 +23,7 @@ static NSString *const EZFontSizeUpdateNotification = @"EZFontSizeUpdateNotifica static NSString *const EZIntelligentQueryModeKey = @"IntelligentQueryMode"; + typedef NS_ENUM(NSUInteger, EZLanguageDetectOptimize) { EZLanguageDetectOptimizeNone = 0, EZLanguageDetectOptimizeBaidu = 1, diff --git a/Easydict/Feature/Service/Tencent/TencentTranslateType.swift b/Easydict/Feature/Service/Tencent/TencentTranslateType.swift index e82753154..84ac8b712 100644 --- a/Easydict/Feature/Service/Tencent/TencentTranslateType.swift +++ b/Easydict/Feature/Service/Tencent/TencentTranslateType.swift @@ -75,17 +75,3 @@ struct TencentTranslateType: Equatable { return TencentTranslateType(sourceLanguage: fromLanguage, targetLanguage: toLanguage) } } - -extension [Language] { - /// Contains Chinese language, - func containsChinese() -> Bool { - contains { $0.isKindOfChinese() } - } -} - -extension Language { - /// Is kind of Chinese language, means it is simplifiedChinese or traditionalChinese. - func isKindOfChinese() -> Bool { - self == .simplifiedChinese || self == .traditionalChinese - } -} diff --git a/Easydict/NewApp/Configuration/Configuration.swift b/Easydict/NewApp/Configuration/Configuration.swift new file mode 100644 index 000000000..9ecdff830 --- /dev/null +++ b/Easydict/NewApp/Configuration/Configuration.swift @@ -0,0 +1,55 @@ +// +// Configuration.swift +// Easydict +// +// Created by 戴藏龙 on 2024/1/12. +// Copyright © 2024 izual. All rights reserved. +// + +import Defaults +import Foundation + +extension Defaults.Keys { + // rename `from` + static let queryFromLanguage = Key("EZConfiguration_kFromKey", default: .auto) + // rename `to` + static let queryToLanguage = Key("EZConfiguration_kToKey", default: .auto) + + static let firstLanguage = Key("EZConfiguration_kFirstLanguageKey", default: EZLanguageManager.shared().systemPreferredTwoLanguages[0]) + static let secondLanguage = Key("EZConfiguration_kSecondLanguageKey", default: EZLanguageManager.shared().systemPreferredTwoLanguages[1]) + + static let autoSelectText = Key("EZConfiguration_kAutoSelectTextKey", default: true) + static let forceAutoGetSelectedText = Key("EZConfiguration_kForceAutoGetSelectedText", default: false) + + static let disableEmptyCopyBeep = Key("EZConfiguration_kDisableEmptyCopyBeepKey", default: true) + static let clickQuery = Key("EZConfiguration_kClickQueryKey", default: false) + static let autoPlayAudio = Key("EZConfiguration_kAutoPlayAudioKey", default: true) + static let launchAtStartup = Key("EZConfiguration_kLaunchAtStartupKey", default: false) + static let hideMainWindow = Key("EZConfiguration_kHideMainWindowKey", default: true) + static let autoQueryOCRText = Key("EZConfiguration_kAutoQueryOCTTextKey", default: true) + static let autoQuerySelectedText = Key("EZConfiguration_kAutoQuerySelectedTextKey", default: true) + static let autoQueryPastedText = Key("EZConfiguration_kAutoQueryPastedTextKey", default: false) + static let autoCopyOCRText = Key("EZConfiguration_kAutoCopyOCRTextKey", default: false) + static let autoCopySelectedText = Key("EZConfiguration_kAutoCopySelectedTextKey", default: false) + static let autoCopyFirstTranslatedText = Key("EZConfiguration_kAutoCopyFirstTranslatedTextKey", default: false) + static let languageDetectOptimize = Key("EZConfiguration_kLanguageDetectOptimizeTypeKey", default: EZLanguageDetectOptimize.none) + @available(macOS 13, *) + static let defaultTTSServiceType = Key("EZConfiguration_kDefaultTTSServiceTypeKey", default: TTSServiceType.youdao) + static let showGoogleQuickLink = Key("EZConfiguration_kShowGoogleLinkKey", default: true) + static let showEudicQuickLink = Key("EZConfiguration_kShowEudicLinkKey", default: true) + static let showAppleDictionaryQuickLink = Key("EZConfiguration_kShowAppleDictionaryLinkKey", default: true) + static let hideMenuBarIcon = Key("EZConfiguration_kHideMenuBarIconKey", default: false) + static let fixedWindowPosition = Key("EZConfiguration_kShowFixedWindowPositionKey", default: .right) + static let mouseSelectTranslateWindowType = Key("EZConfiguration_kMouseSelectTranslateWindowTypeKey", default: .mini) + static let shortcutSelectTranslateWindowType = Key("EZConfiguration_kShortcutSelectTranslateWindowTypeKey", default: .fixed) + static let adjustPopButtonOrigin = Key("EZConfiguration_kAdjustPopButtomOriginKey", default: false) + static let allowCrashLog = Key("EZConfiguration_kAllowCrashLogKey", default: true) + static let allowAnalytics = Key("EZConfiguration_kAllowAnalyticsKey", default: true) + static let clearInput = Key("EZConfiguration_kClearInputKey", default: true) + static let enableBetaNewApp = Key("EZConfiguration_kEnableBetaNewAppKey", default: false) + + static let enableBetaFeature = Key("EZBetaFeatureKey", default: false) + + static let appearanceType = Key("EZConfiguration_kApperanceKey", default: .followSystem) + static let fontSizeOptionIndex = Key("EZConfiguration_kTranslationControllerFontKey", default: 0) +} diff --git a/Easydict/NewApp/Model/TTSServiceType.swift b/Easydict/NewApp/Model/TTSServiceType.swift new file mode 100644 index 000000000..6eda5cf16 --- /dev/null +++ b/Easydict/NewApp/Model/TTSServiceType.swift @@ -0,0 +1,80 @@ +// +// TTSServiceType.swift +// Easydict +// +// Created by 戴藏龙 on 2024/1/13. +// Copyright © 2024 izual. All rights reserved. +// + +import Defaults +import Foundation + +@available(macOS 13, *) +enum TTSServiceType: String, CaseIterable, CustomLocalizedStringResourceConvertible { + var localizedStringResource: LocalizedStringResource { + switch self { + case .youdao: + "setting.tts_service.options.youdao" + case .bing: + "setting.tts_service.options.bing" + case .google: + "setting.tts_service.options.google" + case .baidu: + "setting.tts_service.options.baidu" + case .apple: + "setting.tts_service.options.apple" + } + } + + case youdao + case bing + case google + case baidu + case apple +} + +@available(macOS 13, *) +extension TTSServiceType: Defaults.Serializable { + // while in the future, ServiceType was deleted, then you can safely delete this struct and `bridge` + struct TTSServiceTypeBridge: Defaults.Bridge { + func serialize(_ value: TTSServiceType?) -> String? { + guard let value else { return nil } + switch value { + case .youdao: + return ServiceType.youdao.rawValue + case .bing: + return ServiceType.bing.rawValue + case .google: + return ServiceType.google.rawValue + case .baidu: + return ServiceType.baidu.rawValue + case .apple: + return ServiceType.apple.rawValue + } + } + + func deserialize(_ object: String?) -> TTSServiceType? { + guard let object else { return nil } + switch object { + case "Youdao": + return .youdao + case "Bing": + return .bing + case "Google": + return .google + case "Baidu": + return .baidu + case "Apple": + return .apple + default: + return nil + } + } + + typealias Value = TTSServiceType + + typealias Serializable = String + } + + static let bridge = TTSServiceTypeBridge() +} diff --git a/Easydict/NewApp/Utility/Extensions/LanguageDetectOptimizeExtensions.swift b/Easydict/NewApp/Utility/Extensions/LanguageDetectOptimizeExtensions.swift new file mode 100644 index 000000000..82ec281a1 --- /dev/null +++ b/Easydict/NewApp/Utility/Extensions/LanguageDetectOptimizeExtensions.swift @@ -0,0 +1,32 @@ +// +// LanguageDetectOptimizeExtensions.swift +// Easydict +// +// Created by 戴藏龙 on 2024/1/13. +// Copyright © 2024 izual. All rights reserved. +// + +import Defaults +import Foundation + +extension EZLanguageDetectOptimize: Defaults.Serializable {} + +extension EZLanguageDetectOptimize: CaseIterable { + public static let allCases: [EZLanguageDetectOptimize] = [.none, .baidu, .google] +} + +@available(macOS 13, *) +extension EZLanguageDetectOptimize: CustomLocalizedStringResourceConvertible { + public var localizedStringResource: LocalizedStringResource { + switch self { + case .none: + "language_detect_optimize_none" + case .google: + "language_detect_optimize_google" + case .baidu: + "language_detect_optimize_baidu" + @unknown default: + "unknown_option" + } + } +} diff --git a/Easydict/NewApp/Utility/Extensions/LanguageExtensions.swift b/Easydict/NewApp/Utility/Extensions/LanguageExtensions.swift new file mode 100644 index 000000000..b7cc84eae --- /dev/null +++ b/Easydict/NewApp/Utility/Extensions/LanguageExtensions.swift @@ -0,0 +1,78 @@ +// +// LanguageExtensions.swift +// Easydict +// +// Created by 戴藏龙 on 2024/1/13. +// Copyright © 2024 izual. All rights reserved. +// + +import Defaults +import Foundation + +extension Language: Defaults.Serializable {} + +extension Language: CaseIterable { + public static let allCases: [Language] = EZLanguageModel.allLanguagesDict().sortedKeys().map { rawValue in + Language(rawValue: rawValue as String) + } + + public static let allAvailableOptions: [Language] = allCases.filter { language in + language != .auto && language != .classicalChinese + } +} + +public extension Language { + var model: EZLanguageModel { + EZLanguageModel.allLanguagesDict().object(forKey: rawValue as NSString) + } + + var chineseName: String { + model.chineseName + } + + var englishName: String { + model.englishName.rawValue + } + + var localName: String { + model.localName + } + + var flagEmoji: String { + model.flagEmoji + } + + var voiceName: String { + model.voiceName + } + + var localeIdentifier: String { + model.localeIdentifier + } + + var localizedName: String { + if EZLanguageManager.shared().isSystemChineseFirstLanguage() { + chineseName + } else { + if self == .auto { + "Auto" + } else { + englishName + } + } + } +} + +extension [Language] { + /// Contains Chinese language, + func containsChinese() -> Bool { + contains { $0.isKindOfChinese() } + } +} + +extension Language { + /// Is kind of Chinese language, means it is simplifiedChinese or traditionalChinese. + func isKindOfChinese() -> Bool { + self == .simplifiedChinese || self == .traditionalChinese + } +} diff --git a/Easydict/NewApp/Utility/Extensions/ShowWindowPositionExtensions.swift b/Easydict/NewApp/Utility/Extensions/ShowWindowPositionExtensions.swift new file mode 100644 index 000000000..e531d64c1 --- /dev/null +++ b/Easydict/NewApp/Utility/Extensions/ShowWindowPositionExtensions.swift @@ -0,0 +1,34 @@ +// +// ShowWindowPositionExtensions.swift +// Easydict +// +// Created by 戴藏龙 on 2024/1/13. +// Copyright © 2024 izual. All rights reserved. +// + +import Defaults +import Foundation + +extension EZShowWindowPosition: Defaults.Serializable {} + +extension EZShowWindowPosition: CaseIterable { + public static let allCases: [EZShowWindowPosition] = [.right, .mouse, .former, .center] +} + +@available(macOS 13, *) +extension EZShowWindowPosition: CustomLocalizedStringResourceConvertible { + public var localizedStringResource: LocalizedStringResource { + switch self { + case .right: + "fixed_window_position_right" + case .mouse: + "fixed_window_position_mouse" + case .former: + "fixed_window_position_former" + case .center: + "fixed_window_position_center" + @unknown default: + "unknown_option" + } + } +} diff --git a/Easydict/NewApp/Utility/Extensions/WindowTypeExtensions.swift b/Easydict/NewApp/Utility/Extensions/WindowTypeExtensions.swift new file mode 100644 index 000000000..34b60c049 --- /dev/null +++ b/Easydict/NewApp/Utility/Extensions/WindowTypeExtensions.swift @@ -0,0 +1,34 @@ +// +// WindowTypeExtensions.swift +// Easydict +// +// Created by 戴藏龙 on 2024/1/13. +// Copyright © 2024 izual. All rights reserved. +// + +import Defaults +import Foundation + +extension EZWindowType: Defaults.Serializable {} + +public extension EZWindowType { + static let availableOptions: [EZWindowType] = [.mini, .fixed] +} + +@available(macOS 13, *) +extension EZWindowType: CustomLocalizedStringResourceConvertible { + public var localizedStringResource: LocalizedStringResource { + switch self { + case .fixed: + "fixed_window" + case .main: + "main_window" + case .mini: + "mini_window" + case .none: + "none_window" + @unknown default: + "unknown_option" + } + } +} diff --git a/Easydict/NewApp/View/SettingView/Tabs/GeneralTab.swift b/Easydict/NewApp/View/SettingView/Tabs/GeneralTab.swift index e9368eaea..852631707 100644 --- a/Easydict/NewApp/View/SettingView/Tabs/GeneralTab.swift +++ b/Easydict/NewApp/View/SettingView/Tabs/GeneralTab.swift @@ -6,6 +6,7 @@ // Copyright © 2023 izual. All rights reserved. // +import Defaults import SwiftUI @available(macOS 13, *) @@ -13,44 +14,271 @@ struct GeneralTab: View { var body: some View { Form { Section { - HStack { - Text("show_main_window") - Toggle(isOn: $hideMainWindow) { - Text("hide_main_window") + Picker("setting.general.appearance.light_dark_appearance", selection: $appearanceType) { + ForEach(AppearenceType.allCases, id: \.rawValue) { option in + Text(option.title) + .tag(option) } } - HStack { - Text("launch") - Toggle(isOn: $launchAtStartup) { - Text("launch_at_startup") + } header: { + Text("setting.general.appearance.header") + } + Section { + FirstAndSecondLanguageSettingView() + Picker("setting.general.language.language_detect_optimize", selection: $languageDetectOptimize) { + ForEach(EZLanguageDetectOptimize.allCases, id: \.rawValue) { option in + Text(option.localizedStringResource) + .tag(option) + } + } + } header: { + Text("setting.general.language.header") + } + + Section { + Toggle("auto_show_query_icon", isOn: $autoSelectText) + Toggle("force_auto_get_selected_text", isOn: $forceAutoGetSelectedText) + Toggle("click_icon_query_info", isOn: $clickQuery) + Toggle("setting.general.mouse_query.adjust_pop_button_origin", isOn: $adjustPopButtonOrigin) // 调整查询图标位置: + } header: { + Text("setting.general.mouse_query.header") + } + + Section { + Toggle("setting.general.voice.disable_empty_copy_beep_msg", isOn: $disableEmptyCopyBeep) // 禁用提示音:划词内容为空时生效 + Toggle("setting.general.voice.auto_play_word_audio", isOn: $autoPlayAudio) // 查询英语单词后自动播放发音 + } header: { + Text("setting.general.voice.header") + } + + Section { + Picker("setting.general.window.mouse_select_translate_window_type", selection: $mouseSelectTranslateWindowType) { + ForEach(EZWindowType.availableOptions, id: \.rawValue) { option in + Text(option.localizedStringResource) + .tag(option) } } - HStack { - Text("menu_bar_icon") - Toggle(isOn: $hideMenuBarIcon) { - Text("hide_menu_bar_icon") + Picker("setting.general.window.shortcut_select_translate_window_type", selection: $shortcutSelectTranslateWindowType) { + ForEach(EZWindowType.availableOptions, id: \.rawValue) { option in + Text(option.localizedStringResource) + .tag(option) } } - HStack { - Text("beta_new_app") - Toggle(isOn: $enableBetaNewApp) { - Text("enable_beta_new_app") + Picker("setting.general.window.fixed_window_position", selection: $fixedWindowPosition) { + ForEach(EZShowWindowPosition.allCases, id: \.rawValue) { option in + Text(option.localizedStringResource) + .tag(option) } } + } header: { + Text("setting.general.windows.header") + } + + Section { + Toggle("clear_input_when_translating", isOn: $clearInput) + } header: { + Text("setting.general.input.header") + } + + Section { + Toggle("auto_query_ocr_text", isOn: $autoQueryOCRText) + Toggle("auto_query_selected_text", isOn: $autoQuerySelectedText) + Toggle("auto_query_pasted_text", isOn: $autoQueryPastedText) + } header: { + Text("setting.general.auto_query.header") + } + + Section { + Toggle("auto_copy_ocr_text", isOn: $autoCopyOCRText) + Toggle("auto_copy_selected_text", isOn: $autoCopySelectedText) + Toggle("auto_copy_first_translated_text", isOn: $autoCopyFirstTranslatedText) + } header: { + Text("setting.general.auto_copy.header") + } + + Section { + Toggle("show_google_quick_link", isOn: $showGoogleQuickLink) + Toggle("show_eudic_quick_link", isOn: $showEudicQuickLink) + Toggle("show_apple_dictionary_quick_link", isOn: $showAppleDictionaryQuickLink) + } header: { + Text("setting.general.quick_link.header") + } + + Section { + let bindingFontSize = Binding(get: { + Double(fontSizeOptionIndex) + }, set: { newValue in + fontSizeOptionIndex = UInt(newValue) + }) + Slider(value: bindingFontSize, in: 0.0 ... 4.0, step: 1) { + Text("setting.general.font.font_size.label") + } minimumValueLabel: { + Text("small") + .font(.system(size: 10)) + } maximumValueLabel: { + Text("large") + .font(.system(size: 14)) + } + } header: { + Text("setting.general.font.header") + } footer: { + Text("hints_keyboard_shortcuts_font_size") + .font(.footnote) + } + + Section { + Toggle(isOn: $launchAtStartup) { + Text("launch_at_startup") + } + Toggle(isOn: $hideMainWindow) { + Text("hide_main_window") + } + Toggle(isOn: $hideMenuBarIcon) { + Text("hide_menu_bar_icon") + } } header: { Text("other") } + + Section { + Picker("setting.general.advance.default_tts_service", selection: $defaultTTSServiceType) { + ForEach(TTSServiceType.allCases, id: \.rawValue) { option in + Text(option.localizedStringResource) + .tag(option) + } + } + Toggle("setting.general.advance.enable_beta_feature", isOn: $enableBetaFeature) + Toggle(isOn: $enableBetaNewApp) { + Text("enable_beta_new_app") + } + } header: { + Text("setting.general.advance.header") + } } .formStyle(.grouped) } - @AppStorage(kHideMainWindowKey) private var hideMainWindow = false - @AppStorage(kLaunchAtStartupKey) private var launchAtStartup = false - @AppStorage(kHideMenuBarIconKey) private var hideMenuBarIcon = false - @AppStorage(kEnableBetaNewAppKey) private var enableBetaNewApp = false + @Default(.autoSelectText) private var autoSelectText + @Default(.forceAutoGetSelectedText) private var forceAutoGetSelectedText + @Default(.clickQuery) private var clickQuery + @Default(.adjustPopButtonOrigin) private var adjustPopButtonOrigin + + @Default(.clearInput) private var clearInput + + @Default(.disableEmptyCopyBeep) private var disableEmptyCopyBeep + @Default(.autoPlayAudio) private var autoPlayAudio + + @Default(.autoQueryOCRText) private var autoQueryOCRText + @Default(.autoQuerySelectedText) private var autoQuerySelectedText + @Default(.autoQueryPastedText) private var autoQueryPastedText + + @Default(.autoCopyOCRText) private var autoCopyOCRText + @Default(.autoCopySelectedText) private var autoCopySelectedText + @Default(.autoCopyFirstTranslatedText) private var autoCopyFirstTranslatedText + + @Default(.showGoogleQuickLink) private var showGoogleQuickLink + @Default(.showEudicQuickLink) private var showEudicQuickLink + @Default(.showAppleDictionaryQuickLink) private var showAppleDictionaryQuickLink + + @Default(.hideMainWindow) private var hideMainWindow + @Default(.launchAtStartup) private var launchAtStartup + @Default(.hideMenuBarIcon) private var hideMenuBarIcon + @Default(.enableBetaNewApp) private var enableBetaNewApp + + @Default(.languageDetectOptimize) private var languageDetectOptimize + @Default(.defaultTTSServiceType) private var defaultTTSServiceType + + @Default(.fixedWindowPosition) private var fixedWindowPosition + @Default(.mouseSelectTranslateWindowType) private var mouseSelectTranslateWindowType + @Default(.shortcutSelectTranslateWindowType) private var shortcutSelectTranslateWindowType + @Default(.enableBetaFeature) private var enableBetaFeature + @Default(.appearanceType) private var appearanceType + + @Default(.fontSizeOptionIndex) private var fontSizeOptionIndex } @available(macOS 13, *) #Preview { GeneralTab() } + +@available(macOS 13, *) +private struct FirstAndSecondLanguageSettingView: View { + var body: some View { + Group { + Picker("setting.general.language.first_language", selection: $firstLanguage) { + ForEach(Language.allAvailableOptions, id: \.rawValue) { option in + Text(verbatim: "\(option.flagEmoji) \(option.localizedName)") + .tag(option) + } + } + Picker("setting.general.language.second_language", selection: $secondLanguage) { + ForEach(Language.allAvailableOptions, id: \.rawValue) { option in + Text(verbatim: "\(option.flagEmoji) \(option.localizedName)") + .tag(option) + } + } + } + .onChange(of: firstLanguage) { [firstLanguage] newValue in + let oldValue = firstLanguage + if newValue == secondLanguage { + secondLanguage = oldValue + languageDuplicatedAlert = .init(duplicatedLanguage: newValue, setField: .second, setLanguage: oldValue) + } + } + .onChange(of: secondLanguage) { [secondLanguage] newValue in + let oldValue = secondLanguage + if newValue == firstLanguage { + firstLanguage = oldValue + languageDuplicatedAlert = .init(duplicatedLanguage: newValue, setField: .first, setLanguage: oldValue) + } + } + .alert("setting.general.language.duplicated_alert.title", isPresented: showLanguageDuplicatedAlert, presenting: languageDuplicatedAlert) { _ in + + } message: { alert in + Text(alert.description) + } + } + + @Default(.firstLanguage) private var firstLanguage + @Default(.secondLanguage) private var secondLanguage + + private struct LanguageDuplicateAlert: CustomStringConvertible { + var description: String { + // First language should not be same as second language. (\(duplicatedLanguage)) + // \(setField) is replaced with \(setLanguage). + String(localized: "setting.general.language.duplicated_alert \(duplicatedLanguage.localizedName)\(String(localized: setField.localizedStringResource))\(setLanguage.localizedName)") + } + + let duplicatedLanguage: Language + + let setField: Field + + let setLanguage: Language + + enum Field: CustomLocalizedStringResourceConvertible { + var localizedStringResource: LocalizedStringResource { + switch self { + case .first: + "setting.general.language.duplicated_alert.field.first" + case .second: + "setting.general.language.duplicated_alert.field.second" + } + } + + case first + case second + } + } + + @State private var languageDuplicatedAlert: LanguageDuplicateAlert? + private var showLanguageDuplicatedAlert: Binding { + .init { + languageDuplicatedAlert != nil + } set: { newValue in + if !newValue { + languageDuplicatedAlert = nil + } + } + } +}