diff --git a/Easydict.xcodeproj/project.pbxproj b/Easydict.xcodeproj/project.pbxproj index 5b05e2fc2..154339362 100644 --- a/Easydict.xcodeproj/project.pbxproj +++ b/Easydict.xcodeproj/project.pbxproj @@ -180,6 +180,7 @@ 03BFFC6E295FE59C004E033E /* EZQueryResult+EZYoudaoDictModel.m in Sources */ = {isa = PBXBuildFile; fileRef = 03BFFC6D295FE59C004E033E /* EZQueryResult+EZYoudaoDictModel.m */; }; 03BFFC7129612E10004E033E /* NSString+EZConvenience.m in Sources */ = {isa = PBXBuildFile; fileRef = 03BFFC7029612E10004E033E /* NSString+EZConvenience.m */; }; 03CAB9552ADBF0FF00DA94A3 /* EZSystemUtility.m in Sources */ = {isa = PBXBuildFile; fileRef = 03CAB9542ADBF0FF00DA94A3 /* EZSystemUtility.m */; }; + 03CF88632B137F650030C199 /* Array+ChineseTextConversion.swift in Sources */ = {isa = PBXBuildFile; fileRef = 03CF88622B137F650030C199 /* Array+ChineseTextConversion.swift */; }; 03D0434E292886D200E7559E /* EZMiniQueryWindow.m in Sources */ = {isa = PBXBuildFile; fileRef = 03D0434D292886D200E7559E /* EZMiniQueryWindow.m */; }; 03D043522928935300E7559E /* EZMainQueryWindow.m in Sources */ = {isa = PBXBuildFile; fileRef = 03D043512928935300E7559E /* EZMainQueryWindow.m */; }; 03D043562928940500E7559E /* EZBaseQueryWindow.m in Sources */ = {isa = PBXBuildFile; fileRef = 03D043552928940500E7559E /* EZBaseQueryWindow.m */; }; @@ -209,6 +210,10 @@ 03F639952AA6CFBB009B9914 /* EZBingConfig.m in Sources */ = {isa = PBXBuildFile; fileRef = 03F639942AA6CFBB009B9914 /* EZBingConfig.m */; }; 17BCAEF72B0DFF9000A7D372 /* EZNiuTransTranslateResponse.m in Sources */ = {isa = PBXBuildFile; fileRef = 17BCAEF52B0DFF9000A7D372 /* EZNiuTransTranslateResponse.m */; }; 17BCAEF82B0DFF9000A7D372 /* EZNiuTransTranslate.m in Sources */ = {isa = PBXBuildFile; fileRef = 17BCAEF62B0DFF9000A7D372 /* EZNiuTransTranslate.m */; }; + 2721E4D02AFE920700A059AC /* Alamofire in Frameworks */ = {isa = PBXBuildFile; productRef = 2721E4CF2AFE920700A059AC /* Alamofire */; }; + 2746AEC12AF95138005FE0A1 /* CaiyunService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2746AEC02AF95138005FE0A1 /* CaiyunService.swift */; }; + 278322602B0FB0EA0026644C /* CaiyunResponse.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2783225F2B0FB0EA0026644C /* CaiyunResponse.swift */; }; + 278322622B0FB8EF0026644C /* CaiyunTranslateType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 278322612B0FB8EF0026644C /* CaiyunTranslateType.swift */; }; 27B7919E2AEC36A1006E07C6 /* Easydict.xcconfig in Resources */ = {isa = PBXBuildFile; fileRef = 27B7919C2AEC36A1006E07C6 /* Easydict.xcconfig */; }; 27B7919F2AEC36A1006E07C6 /* Easydict-debug.xcconfig in Resources */ = {isa = PBXBuildFile; fileRef = 27B7919D2AEC36A1006E07C6 /* Easydict-debug.xcconfig */; }; 6220AD5B2A82812300BBFB52 /* EZBingService.m in Sources */ = {isa = PBXBuildFile; fileRef = 6220AD5A2A82812300BBFB52 /* EZBingService.m */; }; @@ -580,6 +585,7 @@ 03BFFC7029612E10004E033E /* NSString+EZConvenience.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = "NSString+EZConvenience.m"; sourceTree = ""; }; 03CAB9532ADBF0FF00DA94A3 /* EZSystemUtility.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = EZSystemUtility.h; sourceTree = ""; }; 03CAB9542ADBF0FF00DA94A3 /* EZSystemUtility.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = EZSystemUtility.m; sourceTree = ""; }; + 03CF88622B137F650030C199 /* Array+ChineseTextConversion.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Array+ChineseTextConversion.swift"; sourceTree = ""; }; 03D0434C292886D200E7559E /* EZMiniQueryWindow.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = EZMiniQueryWindow.h; sourceTree = ""; }; 03D0434D292886D200E7559E /* EZMiniQueryWindow.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = EZMiniQueryWindow.m; sourceTree = ""; }; 03D043502928935300E7559E /* EZMainQueryWindow.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = EZMainQueryWindow.h; sourceTree = ""; }; @@ -635,6 +641,9 @@ 17BCAEF42B0DFF9000A7D372 /* EZNiuTransTranslate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = EZNiuTransTranslate.h; sourceTree = ""; }; 17BCAEF52B0DFF9000A7D372 /* EZNiuTransTranslateResponse.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = EZNiuTransTranslateResponse.m; sourceTree = ""; }; 17BCAEF62B0DFF9000A7D372 /* EZNiuTransTranslate.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = EZNiuTransTranslate.m; sourceTree = ""; }; + 2746AEC02AF95138005FE0A1 /* CaiyunService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CaiyunService.swift; sourceTree = ""; }; + 2783225F2B0FB0EA0026644C /* CaiyunResponse.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CaiyunResponse.swift; sourceTree = ""; }; + 278322612B0FB8EF0026644C /* CaiyunTranslateType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CaiyunTranslateType.swift; sourceTree = ""; }; 27B7919C2AEC36A1006E07C6 /* Easydict.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; path = Easydict.xcconfig; sourceTree = ""; }; 27B7919D2AEC36A1006E07C6 /* Easydict-debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; path = "Easydict-debug.xcconfig"; sourceTree = ""; }; 27B791A02AEC3A5C006E07C6 /* Easydict-debug.entitlements */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.entitlements; path = "Easydict-debug.entitlements"; sourceTree = ""; }; @@ -677,6 +686,7 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + 2721E4D02AFE920700A059AC /* Alamofire in Frameworks */, B87AC7E36367075BA5D13234 /* Pods_Easydict.framework in Frameworks */, 03B63ABF2A86967800E155ED /* CoreServices.framework in Frameworks */, ); @@ -1013,6 +1023,7 @@ 038954372A25A94E00EFFDC3 /* Utility */ = { isa = PBXGroup; children = ( + 03CF88602B137ECB0030C199 /* Swift */, 03CAB9522ADBEF5000DA94A3 /* SystemUtility */, 030570DF2ADB916E00C9905E /* AppleScript */, 03D8B26A292DBC8800D5A811 /* EZCategory */, @@ -1151,6 +1162,7 @@ isa = PBXGroup; children = ( 17BCAEF22B0DFF9000A7D372 /* Niutrans */, + 2746AEBF2AF95040005FE0A1 /* Caiyun */, 6220AD582A8280E800BBFB52 /* Bing */, 0399C6A929A8608000B4AFCC /* OpenAI */, 03F14A382956011400CB7379 /* Volcano */, @@ -1661,6 +1673,22 @@ path = SystemUtility; sourceTree = ""; }; + 03CF88602B137ECB0030C199 /* Swift */ = { + isa = PBXGroup; + children = ( + 03CF88612B137ED60030C199 /* Array */, + ); + path = Swift; + sourceTree = ""; + }; + 03CF88612B137ED60030C199 /* Array */ = { + isa = PBXGroup; + children = ( + 03CF88622B137F650030C199 /* Array+ChineseTextConversion.swift */, + ); + path = Array; + sourceTree = ""; + }; 03D0434A292884B900E7559E /* MiniQueryWindow */ = { isa = PBXGroup; children = ( @@ -1828,6 +1856,16 @@ path = Niutrans; sourceTree = ""; }; + 2746AEBF2AF95040005FE0A1 /* Caiyun */ = { + isa = PBXGroup; + children = ( + 2746AEC02AF95138005FE0A1 /* CaiyunService.swift */, + 2783225F2B0FB0EA0026644C /* CaiyunResponse.swift */, + 278322612B0FB8EF0026644C /* CaiyunTranslateType.swift */, + ); + path = Caiyun; + sourceTree = ""; + }; 6220AD582A8280E800BBFB52 /* Bing */ = { isa = PBXGroup; children = ( @@ -1957,6 +1995,9 @@ dependencies = ( ); name = Easydict; + packageProductDependencies = ( + 2721E4CF2AFE920700A059AC /* Alamofire */, + ); productName = Bob; productReference = C99EEB182385796700FEE666 /* Easydict-Debug.app */; productType = "com.apple.product-type.application"; @@ -1995,6 +2036,9 @@ "zh-Hans", ); mainGroup = C99EEB0F2385796700FEE666; + packageReferences = ( + 2721E4CE2AFE920700A059AC /* XCRemoteSwiftPackageReference "Alamofire" */, + ); productRefGroup = C99EEB192385796700FEE666 /* Products */; projectDirPath = ""; projectRoot = ""; @@ -2222,6 +2266,7 @@ 03B0230629231FA6001C7E63 /* EZLabel.m in Sources */, 03F25CB329327BC200E66A12 /* EZShortcut.m in Sources */, 033B7134293CE2430096E2DF /* EZWebViewTranslator.m in Sources */, + 03CF88632B137F650030C199 /* Array+ChineseTextConversion.swift in Sources */, 03B0231229231FA6001C7E63 /* NSObject+DarkMode.m in Sources */, 03B0233829231FA6001C7E63 /* MMOrderedDictionary.m in Sources */, 03BDA7BC2A26DA280079D04F /* XPMArgumentSignature.m in Sources */, @@ -2241,6 +2286,7 @@ 03B0232329231FA6001C7E63 /* NSString+MM.m in Sources */, 036196772A000F5900806370 /* NSData+CommonCrypto.m in Sources */, 03882F8D29D95044005B5A52 /* CTView.m in Sources */, + 278322602B0FB0EA0026644C /* CaiyunResponse.swift in Sources */, 03B3B8B52925DD3D00168E8D /* EZPopButtonViewController.m in Sources */, 03542A5B2938DA2B00C34C33 /* EZDetectLanguageButton.m in Sources */, 03B0232929231FA6001C7E63 /* NSDictionary+MM.m in Sources */, @@ -2313,11 +2359,13 @@ 17BCAEF72B0DFF9000A7D372 /* EZNiuTransTranslateResponse.m in Sources */, 03BDA7B82A26DA280079D04F /* XPMValuedArgument.m in Sources */, 036196762A000F5900806370 /* NSData+Base64.m in Sources */, + 278322622B0FB8EF0026644C /* CaiyunTranslateType.swift in Sources */, 03BDA7BA2A26DA280079D04F /* XPMMutableAttributedArray.m in Sources */, 037852B629588EDE00D0E2CF /* EZCustomTableRowView.m in Sources */, 03F0DB382953428300EBF9C1 /* EZLog.m in Sources */, 03B0231429231FA6001C7E63 /* DarkModeManager.m in Sources */, 03BDA7C02A26DA280079D04F /* XPMArgumentPackage.m in Sources */, + 2746AEC12AF95138005FE0A1 /* CaiyunService.swift in Sources */, 037852B02957FEB200D0E2CF /* EZServiceViewController.m in Sources */, 6220AD5B2A82812300BBFB52 /* EZBingService.m in Sources */, 039F5508294B6E29004AB940 /* EZAboutViewController.m in Sources */, @@ -2718,6 +2766,25 @@ defaultConfigurationName = Release; }; /* End XCConfigurationList section */ + +/* Begin XCRemoteSwiftPackageReference section */ + 2721E4CE2AFE920700A059AC /* XCRemoteSwiftPackageReference "Alamofire" */ = { + isa = XCRemoteSwiftPackageReference; + repositoryURL = "https://github.com/Alamofire/Alamofire.git"; + requirement = { + kind = upToNextMajorVersion; + minimumVersion = 5.8.1; + }; + }; +/* End XCRemoteSwiftPackageReference section */ + +/* Begin XCSwiftPackageProductDependency section */ + 2721E4CF2AFE920700A059AC /* Alamofire */ = { + isa = XCSwiftPackageProductDependency; + package = 2721E4CE2AFE920700A059AC /* XCRemoteSwiftPackageReference "Alamofire" */; + productName = Alamofire; + }; +/* End XCSwiftPackageProductDependency section */ }; rootObject = C99EEB102385796700FEE666 /* Project object */; } diff --git a/Easydict.xcworkspace/xcshareddata/swiftpm/Package.resolved b/Easydict.xcworkspace/xcshareddata/swiftpm/Package.resolved new file mode 100644 index 000000000..bc7ce257b --- /dev/null +++ b/Easydict.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -0,0 +1,14 @@ +{ + "pins" : [ + { + "identity" : "alamofire", + "kind" : "remoteSourceControl", + "location" : "https://github.com/Alamofire/Alamofire.git", + "state" : { + "revision" : "3dc6a42c7727c49bf26508e29b0a0b35f9c7e1ad", + "version" : "5.8.1" + } + } + ], + "version" : 2 +} diff --git a/Easydict/App/Easydict-Bridging-Header.h b/Easydict/App/Easydict-Bridging-Header.h index 724abb748..edf8fcb2d 100644 --- a/Easydict/App/Easydict-Bridging-Header.h +++ b/Easydict/App/Easydict-Bridging-Header.h @@ -3,3 +3,8 @@ // #import "MMLog.h" + +#import "EZQueryService.h" +#import "EZConstKey.h" +#import "FWEncryptorAES.h" +#import "NSString+EZChineseText.h" diff --git a/Easydict/App/Localizable.xcstrings b/Easydict/App/Localizable.xcstrings index 893731daf..d5359222d 100644 --- a/Easydict/App/Localizable.xcstrings +++ b/Easydict/App/Localizable.xcstrings @@ -408,12 +408,29 @@ "localizations" : { "zh-Hans" : { "stringUnit" : { - "state" : "needs_review", + "state" : "translated", "value" : "" } } } }, + "caiyun_translate" : { + "comment" : "The name of Caiyun Translate", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "LingoCloud" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "彩云小译" + } + } + } + }, "cancel" : { "localizations" : { "en" : { @@ -595,7 +612,7 @@ "localizations" : { "zh-Hans" : { "stringUnit" : { - "state" : "needs_review", + "state" : "translated", "value" : "" } } @@ -865,7 +882,7 @@ "localizations" : { "zh-Hans" : { "stringUnit" : { - "state" : "needs_review", + "state" : "translated", "value" : "" } } @@ -1036,7 +1053,7 @@ "localizations" : { "zh-Hans" : { "stringUnit" : { - "state" : "needs_review", + "state" : "translated", "value" : "" } } @@ -1137,7 +1154,7 @@ "localizations" : { "zh-Hans" : { "stringUnit" : { - "state" : "needs_review", + "state" : "translated", "value" : "" } } @@ -1148,7 +1165,7 @@ "localizations" : { "zh-Hans" : { "stringUnit" : { - "state" : "needs_review", + "state" : "translated", "value" : "" } } @@ -1159,7 +1176,7 @@ "localizations" : { "zh-Hans" : { "stringUnit" : { - "state" : "needs_review", + "state" : "translated", "value" : "" } } @@ -1186,7 +1203,7 @@ "localizations" : { "zh-Hans" : { "stringUnit" : { - "state" : "needs_review", + "state" : "translated", "value" : "" } } @@ -1325,7 +1342,7 @@ "localizations" : { "zh-Hans" : { "stringUnit" : { - "state" : "needs_review", + "state" : "translated", "value" : "" } } @@ -1379,6 +1396,22 @@ } } }, + "niuTrans_translate" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "NiuTrans" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "小牛翻译" + } + } + } + }, "no_results_found" : { "localizations" : { "en" : { @@ -1512,7 +1545,7 @@ "localizations" : { "zh-Hans" : { "stringUnit" : { - "state" : "needs_review", + "state" : "translated", "value" : "" } } @@ -1769,7 +1802,7 @@ }, "zh-Hans" : { "stringUnit" : { - "state" : "needs_review", + "state" : "translated", "value" : "使用译文替换" } } @@ -2053,7 +2086,7 @@ "localizations" : { "zh-Hans" : { "stringUnit" : { - "state" : "needs_review", + "state" : "translated", "value" : "" } } @@ -2128,7 +2161,7 @@ "localizations" : { "zh-Hans" : { "stringUnit" : { - "state" : "needs_review", + "state" : "translated", "value" : "" } } @@ -2139,7 +2172,7 @@ "localizations" : { "zh-Hans" : { "stringUnit" : { - "state" : "needs_review", + "state" : "translated", "value" : "" } } @@ -2161,50 +2194,50 @@ } } }, - "us_phonetic" : { + "unsupported_translation_type" : { "localizations" : { "en" : { "stringUnit" : { "state" : "translated", - "value" : "US:" + "value" : "Unsupported translation type" } }, "zh-Hans" : { "stringUnit" : { "state" : "translated", - "value" : "美:" + "value" : "不支持的翻译类型" } } } }, - "volcano_translate" : { + "us_phonetic" : { "localizations" : { "en" : { "stringUnit" : { "state" : "translated", - "value" : "Volcano Translate" + "value" : "US:" } }, "zh-Hans" : { "stringUnit" : { "state" : "translated", - "value" : "火山翻译" + "value" : "美:" } } } }, - "niuTrans_translate" : { + "volcano_translate" : { "localizations" : { "en" : { "stringUnit" : { "state" : "translated", - "value" : "NiuTrans" + "value" : "Volcano Translate" } }, "zh-Hans" : { "stringUnit" : { "state" : "translated", - "value" : "小牛翻译" + "value" : "火山翻译" } } } diff --git a/Easydict/Feature/PerferenceWindow/EZSettingViewController.m b/Easydict/Feature/PerferenceWindow/EZSettingViewController.m index 87c499b5c..b171378b2 100644 --- a/Easydict/Feature/PerferenceWindow/EZSettingViewController.m +++ b/Easydict/Feature/PerferenceWindow/EZSettingViewController.m @@ -130,6 +130,7 @@ @implementation EZSettingViewController _enabledTTSServiceTypes = @[ EZServiceTypeYoudao, EZServiceTypeBing, + EZServiceTypeCaiyun, EZServiceTypeGoogle, EZServiceTypeBaidu, EZServiceTypeApple, diff --git a/Easydict/Feature/Service/Caiyun/CaiyunResponse.swift b/Easydict/Feature/Service/Caiyun/CaiyunResponse.swift new file mode 100644 index 000000000..9225955b4 --- /dev/null +++ b/Easydict/Feature/Service/Caiyun/CaiyunResponse.swift @@ -0,0 +1,15 @@ +// +// CaiyunResponse.swift +// Easydict +// +// Created by Kyle on 2023/11/24. +// Copyright © 2023 izual. All rights reserved. +// + +import Foundation + +struct CaiyunResponse: Codable { + var confidence: Double + var rc: Int + var target: [String] +} diff --git a/Easydict/Feature/Service/Caiyun/CaiyunService.swift b/Easydict/Feature/Service/Caiyun/CaiyunService.swift new file mode 100644 index 000000000..2a8c44e9d --- /dev/null +++ b/Easydict/Feature/Service/Caiyun/CaiyunService.swift @@ -0,0 +1,113 @@ +// +// CaiyunService.swift +// Easydict +// +// Created by Kyle on 2023/11/7. +// Copyright © 2023 izual. All rights reserved. +// + +import Alamofire +import Foundation + +@objc(EZCaiyunService) +public final class CaiyunService: QueryService { + override public func serviceType() -> ServiceType { + .caiyun + } + + override public func link() -> String? { + "https://fanyi.caiyunapp.com" + } + + override public func name() -> String { + NSLocalizedString("caiyun_translate", comment: "The name of Caiyun Translate") + } + + override public func supportLanguagesDictionary() -> MMOrderedDictionary { + // TODO: Replace MMOrderedDictionary. + let orderedDict = MMOrderedDictionary() + CaiyunTranslateType.supportLanguagesDictionary.forEach { key, value in + orderedDict.setObject(value as NSString, forKey: key.rawValue as NSString) + } + return orderedDict + } + + override public func ocr(_: EZQueryModel) async throws -> EZOCRResult { + NSLog("Caiyun Translate does not support OCR") + throw QueryServiceError.notSupported + } + + private var apiEndPoint = "https://api.interpreter.caiyunai.com/v1/translator" + + /// Official Test Token for Caiyun + private static let defaultTestToken = FWEncryptorAES.decryptText("hlvDXvvfjeFTjMjhkB5HMlyPWEXQhn3U1r+qIqn/YAk=", key: "Easydict") + + private var token: String { + let token = UserDefaults.standard.string(forKey: EZCaiyunToken) + if let token, !token.isEmpty { + return token + } else { + return CaiyunService.defaultTestToken + } + } + + override public func autoConvertToTraditionalChineseResult() -> Bool { + return true + } + + public override func translate(_ text: String, from: Language, to: Language, completion: @escaping (EZQueryResult, Error?) -> Void) { + if prehandleQueryTextLanguage(text, autoConvertChineseText: true, from: from, to: to, completion: completion) { + return + } + let transType = CaiyunTranslateType.transType(from: from, to: to) + guard transType != .unsupported else { + result.errorType = .unsupportedLanguage + let unsupportedType = NSLocalizedString("unsupported_translation_type", comment: "") + result.errorMessage = "\(unsupportedType): \(from.rawValue) --> \(to.rawValue)" + completion(result, nil) + return + } + + // Docs: https://docs.caiyunapp.com/blog/ + let parameters: [String: Any] = [ + "source": text.split(separator: "\n", omittingEmptySubsequences: false), + "trans_type": transType.rawValue, + "media": "text", + "request_id": "Easydict", + "detect": true, + ] + let headers: HTTPHeaders = [ + "content-type": "application/json", + "x-authorization": "token " + token, + ] + + let request = AF.request(apiEndPoint, + method: .post, + parameters: parameters, + encoding: JSONEncoding.default, + headers: headers) + .validate() + .responseDecodable(of: CaiyunResponse.self) { [weak self] response in + guard let self else { return } + let result = self.result + switch response.result { + case let .success(value): + result.from = from + result.to = to + result.queryText = text + result.translatedResults = value.target + completion(result, nil) + case let .failure(error): + NSLog("Caiyun lookup error \(error)") + completion(result, error) + } + } + queryModel.setStop({ + request.cancel() + }, serviceType: serviceType().rawValue) + } +} + +enum QueryServiceError: Error { + case notSupported +} diff --git a/Easydict/Feature/Service/Caiyun/CaiyunTranslateType.swift b/Easydict/Feature/Service/Caiyun/CaiyunTranslateType.swift new file mode 100644 index 000000000..22c19dbb5 --- /dev/null +++ b/Easydict/Feature/Service/Caiyun/CaiyunTranslateType.swift @@ -0,0 +1,52 @@ +// +// CaiyunTranslateType.swift +// Easydict +// +// Created by Kyle on 2023/11/24. +// Copyright © 2023 izual. All rights reserved. +// + +import Foundation + +struct CaiyunTranslateType: RawRepresentable { + var rawValue: String + + static let unsupported = CaiyunTranslateType(rawValue: "unsupported") + + // Align with the web interface https://fanyi.caiyunapp.com/#/ + static let supportedTypes: [Language: [Language]] = [ + .simplifiedChinese: [.english, .japanese, .korean, .spanish, .french, .russian], + .english: [.simplifiedChinese, .spanish, .french, .russian], + .japanese: [.simplifiedChinese], + .korean: [.simplifiedChinese], + .spanish: [.simplifiedChinese, .english, .french, .russian], + .french: [.simplifiedChinese, .english, .spanish, .russian], + .russian: [.simplifiedChinese, .english, .spanish, .french], + ] + + static let supportLanguagesDictionary: [Language: String] = [ + .auto: "auto", + .simplifiedChinese: "zh", + .traditionalChinese: "zh", + .english: "en", + .japanese: "ja", + .korean: "ko", + .french: "fr", + .spanish: "es", + .russian: "ru", + ] + + static func transType(from: Language, to: Language) -> CaiyunTranslateType { + // We can auto convert to Traditional Chinese. + if (supportedTypes[from] != nil && to == .traditionalChinese) || + (supportedTypes[from]?.contains(to) == true) { + guard let from = supportLanguagesDictionary[from], + let to = supportLanguagesDictionary[to] else { + return .unsupported + } + return CaiyunTranslateType(rawValue: "\(from)2\(to)") + } else { + return .unsupported + } + } +} diff --git a/Easydict/Feature/Service/DeepL/EZDeepLTranslate.m b/Easydict/Feature/Service/DeepL/EZDeepLTranslate.m index 6ab27406c..2b4cc0edb 100644 --- a/Easydict/Feature/Service/DeepL/EZDeepLTranslate.m +++ b/Easydict/Feature/Service/DeepL/EZDeepLTranslate.m @@ -10,7 +10,6 @@ #import "EZWebViewTranslator.h" #import "EZTranslateError.h" #import "EZQueryResult+EZDeepLTranslateResponse.h" -#import "NSArray+EZChineseText.h" static NSString *kDeepLTranslateURL = @"https://www.deepl.com/translator"; @@ -134,25 +133,10 @@ - (void)translate:(NSString *)text from:(EZLanguage)from to:(EZLanguage)to compl return; } - mm_weakify(self); - [self setDidFinishBlock:^(EZQueryResult *result, NSError *error) { - mm_strongify(self); - NSArray *texts = result.translatedResults; - if ([self.queryModel.queryTargetLanguage isEqualToString:EZLanguageTraditionalChinese]) { - texts = [texts toTraditionalChineseTexts]; - } - result.translatedResults = texts; - }]; - - void (^callback)(EZQueryResult *result, NSError *error) = ^(EZQueryResult *result, NSError *error) { - self.didFinishBlock(result, error); - completion(result, error); - }; - if (self.apiType == EZDeepLTranslationAPIWebFirst) { - [self deepLWebTranslate:text from:from to:to completion:callback]; + [self deepLWebTranslate:text from:from to:to completion:completion]; } else { - [self deepLTranslate:text from:from to:to completion:callback]; + [self deepLTranslate:text from:from to:to completion:completion]; } } @@ -160,6 +144,10 @@ - (void)ocr:(EZQueryModel *)queryModel completion:(void (^)(EZOCRResult *_Nullab NSLog(@"deepL not support ocr"); } +- (BOOL)autoConvertToTraditionalChineseResult { + return YES; +} + #pragma mark - WebView Translate - (void)webViewTranslate:(nonnull void (^)(EZQueryResult *, NSError *_Nullable))completion { diff --git a/Easydict/Feature/Service/Google/EZGoogleTranslate.m b/Easydict/Feature/Service/Google/EZGoogleTranslate.m index bb3d13575..48d53f292 100644 --- a/Easydict/Feature/Service/Google/EZGoogleTranslate.m +++ b/Easydict/Feature/Service/Google/EZGoogleTranslate.m @@ -10,7 +10,6 @@ #import "EZYoudaoTranslate.h" #import #import "NSString+EZUtils.h" -#import "NSArray+EZChineseText.h" #import "EZConfiguration.h" static NSString *const kGoogleTranslateURL = @"https://translate.google.com"; diff --git a/Easydict/Feature/Service/Language/EZLanguageManager.h b/Easydict/Feature/Service/Language/EZLanguageManager.h index 16f9ad9b7..5c28f9a53 100644 --- a/Easydict/Feature/Service/Language/EZLanguageManager.h +++ b/Easydict/Feature/Service/Language/EZLanguageManager.h @@ -8,6 +8,7 @@ #import #import "EZLanguageModel.h" +#import "MMOrderedDictionary.h" NS_ASSUME_NONNULL_BEGIN diff --git a/Easydict/Feature/Service/Language/EZLanguageModel.h b/Easydict/Feature/Service/Language/EZLanguageModel.h index 6f2100962..7152182a4 100644 --- a/Easydict/Feature/Service/Language/EZLanguageModel.h +++ b/Easydict/Feature/Service/Language/EZLanguageModel.h @@ -7,11 +7,12 @@ // #import +#import "MMOrderedDictionary.h" NS_ASSUME_NONNULL_BEGIN // Refer Apple NLLanguage. -typedef NSString *EZLanguage NS_STRING_ENUM; +typedef NSString *EZLanguage NS_STRING_ENUM NS_SWIFT_NAME(Language); // 目前总计支持 49 种语言:简体中文,繁体中文,文言文,英语,日语,韩语,法语,西班牙语,葡萄牙语,意大利语,德语,俄语,阿拉伯语,瑞典语,罗马尼亚语,泰语,斯洛伐克语,荷兰语,匈牙利语,希腊语,丹麦语,芬兰语,波兰语,捷克语,土耳其语,立陶宛语,拉脱维亚语,乌克兰语,保加利亚语,印尼语,马来语,斯洛文尼亚语,爱沙尼亚语,越南语,波斯语,印地语,泰卢固语,泰米尔语,乌尔都语,菲律宾语,高棉语,老挝语,孟加拉语,缅甸语,挪威语,塞尔维亚语,克罗地亚语,蒙古语,希伯来语。 diff --git a/Easydict/Feature/Service/Model/EZConstKey.h b/Easydict/Feature/Service/Model/EZConstKey.h index be5472b7b..d698c17d4 100644 --- a/Easydict/Feature/Service/Model/EZConstKey.h +++ b/Easydict/Feature/Service/Model/EZConstKey.h @@ -31,6 +31,7 @@ static NSString *const EZDeepLAuthKey = @"EZDeepLAuthKey"; static NSString *const EZBingCookieKey = @"EZBingCookieKey"; static NSString *const EZNiuTransAPIKey = @"EZNiuTransAPIKey"; +static NSString *const EZCaiyunToken = @"EZCaiyunToken"; @interface EZConstKey : NSObject diff --git a/Easydict/Feature/Service/Model/EZEnumTypes.h b/Easydict/Feature/Service/Model/EZEnumTypes.h index 8c6dd62b0..db285ade9 100644 --- a/Easydict/Feature/Service/Model/EZEnumTypes.h +++ b/Easydict/Feature/Service/Model/EZEnumTypes.h @@ -7,6 +7,7 @@ // #import +#import "MMOrderedDictionary.h" NS_ASSUME_NONNULL_BEGIN @@ -28,7 +29,7 @@ typedef NS_ENUM(NSUInteger, EZShowWindowPosition) { FOUNDATION_EXPORT NSString *const EZServiceTypeKey; -typedef NSString *EZServiceType NS_STRING_ENUM; +typedef NSString *EZServiceType NS_STRING_ENUM NS_SWIFT_NAME(ServiceType); FOUNDATION_EXPORT EZServiceType const EZServiceTypeGoogle; FOUNDATION_EXPORT EZServiceType const EZServiceTypeBaidu; FOUNDATION_EXPORT EZServiceType const EZServiceTypeYoudao; @@ -39,6 +40,7 @@ FOUNDATION_EXPORT EZServiceType const EZServiceTypeOpenAI; FOUNDATION_EXPORT EZServiceType const EZServiceTypeAppleDictionary; FOUNDATION_EXPORT EZServiceType const EZServiceTypeBing; FOUNDATION_EXPORT EZServiceType const EZServiceTypeNiuTrans; +FOUNDATION_EXPORT EZServiceType const EZServiceTypeCaiyun; FOUNDATION_EXPORT NSString *const EZQueryTextTypeKey; FOUNDATION_EXPORT NSString *const EZIntelligentQueryTextTypeKey; diff --git a/Easydict/Feature/Service/Model/EZEnumTypes.m b/Easydict/Feature/Service/Model/EZEnumTypes.m index 2a4ec85e1..22133cb5e 100644 --- a/Easydict/Feature/Service/Model/EZEnumTypes.m +++ b/Easydict/Feature/Service/Model/EZEnumTypes.m @@ -19,6 +19,7 @@ NSString *const EZServiceTypeOpenAI = @"OpenAI"; NSString *const EZServiceTypeBing = @"Bing"; NSString *const EZServiceTypeNiuTrans = @"NiuTrans"; +NSString *const EZServiceTypeCaiyun = @"Caiyun"; NSString *const EZServiceTypeAppleDictionary = @"AppleDictionary"; diff --git a/Easydict/Feature/Service/Model/EZQueryResult.h b/Easydict/Feature/Service/Model/EZQueryResult.h index 64d27d733..041d7c330 100644 --- a/Easydict/Feature/Service/Model/EZQueryResult.h +++ b/Easydict/Feature/Service/Model/EZQueryResult.h @@ -177,6 +177,8 @@ NS_ASSUME_NONNULL_BEGIN - (void)reset; +- (void)convertToTraditionalChineseResult; + @end NS_ASSUME_NONNULL_END diff --git a/Easydict/Feature/Service/Model/EZQueryResult.m b/Easydict/Feature/Service/Model/EZQueryResult.m index bd1ac3eed..24b5cec02 100644 --- a/Easydict/Feature/Service/Model/EZQueryResult.m +++ b/Easydict/Feature/Service/Model/EZQueryResult.m @@ -8,6 +8,7 @@ #import "EZQueryResult.h" #import "EZLocalStorage.h" +#import "NSArray+EZChineseText.h" /// Convert part /** @@ -181,4 +182,8 @@ - (void)reset { self.showReplaceButton = NO; } +- (void)convertToTraditionalChineseResult { + self.translatedResults = [self.translatedResults toTraditionalChineseTexts]; +} + @end diff --git a/Easydict/Feature/Service/Model/EZQueryService.h b/Easydict/Feature/Service/Model/EZQueryService.h index 60bfd60ca..e169fc232 100644 --- a/Easydict/Feature/Service/Model/EZQueryService.h +++ b/Easydict/Feature/Service/Model/EZQueryService.h @@ -14,11 +14,13 @@ #import "EZLayoutManager.h" #import "EZAudioPlayer.h" #import "EZError.h" +#import "MMOrderedDictionary.h" NS_ASSUME_NONNULL_BEGIN //@class EZAudioPlayer; +NS_SWIFT_NAME(QueryService) @interface EZQueryService : NSObject @property (nonatomic, strong) EZQueryModel *queryModel; @@ -37,9 +39,7 @@ NS_ASSUME_NONNULL_BEGIN @property (nonatomic, strong) EZAudioPlayer *audioPlayer; -@property (nonatomic, copy, nullable) void (^didFinishBlock)(EZQueryResult *result, NSError *error); -@property (nonatomic, copy, nullable) void (^autoCopyTranslatedTextBlock)(EZQueryResult *result, NSError *error); - +@property (nonatomic, copy, nullable) void (^autoCopyTranslatedTextBlock)(EZQueryResult *result, NSError *_Nullable error); /// 支持的语言 - (NSArray *)languages; @@ -50,11 +50,9 @@ NS_ASSUME_NONNULL_BEGIN /// 语言字符串转枚举,不支持则返回Auto - (EZLanguage)languageEnumFromCode:(NSString *)langString; - /// 语言在支持的语言数组中的位置,不包含则返回0 - (NSInteger)indexForLanguage:(EZLanguage)lang; - /// 是否提前处理查询,如不支持的语言 /// - Parameters: /// - isAutoConvert: 是否使用本地中文简繁体转换,如 API 服务支持繁简体,则最好交给 API。 @@ -65,6 +63,13 @@ NS_ASSUME_NONNULL_BEGIN @end +/// 子类方法,可选重写 +@interface EZQueryService () + +/// 如果服务不支持繁体中文,可重写返回 YES 来支持。 +- (BOOL)autoConvertToTraditionalChineseResult; + +@end /// 以下方法供子类重写,且必须重写 @interface EZQueryService () diff --git a/Easydict/Feature/Service/Model/EZQueryService.m b/Easydict/Feature/Service/Model/EZQueryService.m index c65697f3e..0d0f34b9a 100644 --- a/Easydict/Feature/Service/Model/EZQueryService.m +++ b/Easydict/Feature/Service/Model/EZQueryService.m @@ -132,7 +132,6 @@ - (void)ocr:(EZQueryModel *)queryModel completion:(void (^)(EZOCRResult *_Nullab [self ocr:queryModel.OCRImage from:queryModel.queryFromLanguage to:queryModel.queryTargetLanguage completion:completion]; } - #pragma mark - 子类重写 - (EZServiceType)serviceType { @@ -166,6 +165,10 @@ - (nullable NSString *)wordLink:(EZQueryModel *)queryModel { return self.link; } +- (BOOL)autoConvertToTraditionalChineseResult { + return NO; +} + - (MMOrderedDictionary *)supportLanguagesDictionary { MethodNotImplemented(); diff --git a/Easydict/Feature/Service/Model/EZServiceTypes.m b/Easydict/Feature/Service/Model/EZServiceTypes.m index e0f6d1515..9deb9c25b 100644 --- a/Easydict/Feature/Service/Model/EZServiceTypes.m +++ b/Easydict/Feature/Service/Model/EZServiceTypes.m @@ -18,6 +18,7 @@ #import "EZConfiguration.h" #import "EZAppleDictionary.h" #import "EZNiuTransTranslate.h" +#import "Easydict-Swift.h" @interface EZServiceTypes () @@ -59,6 +60,7 @@ + (instancetype)allocWithZone:(struct _NSZone *)zone { EZServiceTypeBing, [EZBingService class], EZServiceTypeVolcano, [EZVolcanoTranslate class], EZServiceTypeNiuTrans, [EZNiuTransTranslate class], + EZServiceTypeCaiyun, [EZCaiyunService class], nil]; return allServiceDict; } diff --git a/Easydict/Feature/Service/Niutrans/EZNiuTransTranslate.m b/Easydict/Feature/Service/Niutrans/EZNiuTransTranslate.m index 1ccd55343..3242c651e 100644 --- a/Easydict/Feature/Service/Niutrans/EZNiuTransTranslate.m +++ b/Easydict/Feature/Service/Niutrans/EZNiuTransTranslate.m @@ -7,7 +7,6 @@ // #import "EZNiuTransTranslate.h" -#import "NSArray+EZChineseText.h" #import "EZNiuTransTranslateResponse.h" #import "FWEncryptorAES.h" diff --git a/Easydict/Feature/Service/Youdao/EZYoudaoTranslate.m b/Easydict/Feature/Service/Youdao/EZYoudaoTranslate.m index bab2efe4c..c44f7844e 100644 --- a/Easydict/Feature/Service/Youdao/EZYoudaoTranslate.m +++ b/Easydict/Feature/Service/Youdao/EZYoudaoTranslate.m @@ -20,7 +20,6 @@ #import #import "NSData+EZMD5.h" #import "EZNetworkManager.h" -#import "NSArray+EZChineseText.h" #import "EZConfiguration.h" static NSString *const kYoudaoTranslatetURL = @"https://fanyi.youdao.com"; @@ -293,17 +292,7 @@ - (void)translate:(NSString *)text from:(EZLanguage)from to:(EZLanguage)to compl return; } - [self setDidFinishBlock:^(EZQueryResult *result, NSError *error) { - NSArray *texts = result.translatedResults; - result.translatedResults = texts; - }]; - - void (^callback)(EZQueryResult *result, NSError *error) = ^(EZQueryResult *result, NSError *error) { - self.didFinishBlock(result, error); - completion(result, error); - }; - - [self queryYoudaoDictAndTranslation:text from:from to:to completion:callback]; + [self queryYoudaoDictAndTranslation:text from:from to:to completion:completion]; } - (void)detectText:(NSString *)text completion:(void (^)(EZLanguage, NSError *_Nullable))completion { diff --git a/Easydict/Feature/Utility/EZLinkParser/EZSchemeParser.m b/Easydict/Feature/Utility/EZLinkParser/EZSchemeParser.m index 3983ef0e5..1574be544 100644 --- a/Easydict/Feature/Utility/EZLinkParser/EZSchemeParser.m +++ b/Easydict/Feature/Utility/EZLinkParser/EZSchemeParser.m @@ -204,7 +204,8 @@ - (NSArray *)allowedReadWriteKeys { EZDeepLAuthKey, EZDeepLTranslationAPIKey, EZNiuTransAPIKey, - + EZCaiyunToken, + EZIntelligentQueryModeKey, EZBingCookieKey, diff --git a/Easydict/Feature/Utility/Swift/Array/Array+ChineseTextConversion.swift b/Easydict/Feature/Utility/Swift/Array/Array+ChineseTextConversion.swift new file mode 100644 index 000000000..7531b6268 --- /dev/null +++ b/Easydict/Feature/Utility/Swift/Array/Array+ChineseTextConversion.swift @@ -0,0 +1,18 @@ +// +// Array+ChineseTextConversion.swift +// Easydict +// +// Created by tisfeng on 2023/11/26. +// Copyright © 2023 izual. All rights reserved. +// + +import Foundation + +extension Array where Element == String { + func toTraditionalChineseTexts() -> [String] { + return self.map { text in + let nsStringText = text as NSString + return nsStringText.toTraditionalChineseText() + } + } +} diff --git a/Easydict/Feature/ViewController/Window/BaseQueryWindow/EZBaseQueryViewController.m b/Easydict/Feature/ViewController/Window/BaseQueryWindow/EZBaseQueryViewController.m index 8648a6c33..18c98608c 100644 --- a/Easydict/Feature/ViewController/Window/BaseQueryWindow/EZBaseQueryViewController.m +++ b/Easydict/Feature/ViewController/Window/BaseQueryWindow/EZBaseQueryViewController.m @@ -685,6 +685,11 @@ - (void)queryWithModel:(EZQueryModel *)queryModel } result.error = error; + if (service.autoConvertToTraditionalChineseResult && + [self.queryModel.queryTargetLanguage isEqualToString:EZLanguageTraditionalChinese]) { + [service.result convertToTraditionalChineseResult]; + } + BOOL hideResult = !result.manulShow && !result.hasTranslatedResult && result.isWarningErrorType; if (hideResult) { result.isShowing = NO;