From c662a4dffa510b728c43279b08ed041dc21a657c Mon Sep 17 00:00:00 2001 From: choykarl <253440030@qq.com> Date: Wed, 20 Dec 2023 21:21:58 +0800 Subject: [PATCH 01/16] add ali translate service --- Easydict.xcodeproj/project.pbxproj | 20 ++++ Easydict/App/Easydict-Bridging-Header.h | 1 + Easydict/App/Localizable.xcstrings | 17 ++++ .../Feature/Service/ALi/ALiResponse.swift | 37 +++++++ Easydict/Feature/Service/ALi/ALiService.swift | 97 +++++++++++++++++++ .../Service/ALi/ALiTranslateType.swift | 89 +++++++++++++++++ Easydict/Feature/Service/Model/EZEnumTypes.h | 1 + Easydict/Feature/Service/Model/EZEnumTypes.m | 1 + .../Feature/Service/Model/EZServiceTypes.m | 1 + .../NSString/NSString+EZConvenience.h | 2 + 10 files changed, 266 insertions(+) create mode 100644 Easydict/Feature/Service/ALi/ALiResponse.swift create mode 100644 Easydict/Feature/Service/ALi/ALiService.swift create mode 100644 Easydict/Feature/Service/ALi/ALiTranslateType.swift diff --git a/Easydict.xcodeproj/project.pbxproj b/Easydict.xcodeproj/project.pbxproj index 941531923..18e114f0e 100644 --- a/Easydict.xcodeproj/project.pbxproj +++ b/Easydict.xcodeproj/project.pbxproj @@ -223,6 +223,9 @@ 6295DE312A84D82E006145F4 /* EZBingTranslateModel.m in Sources */ = {isa = PBXBuildFile; fileRef = 6295DE302A84D82E006145F4 /* EZBingTranslateModel.m */; }; 6295DE342A84EF76006145F4 /* EZBingLookupModel.m in Sources */ = {isa = PBXBuildFile; fileRef = 6295DE332A84EF76006145F4 /* EZBingLookupModel.m */; }; 62A2D03F2A82967F007EEB01 /* EZBingRequest.m in Sources */ = {isa = PBXBuildFile; fileRef = 62A2D03E2A82967F007EEB01 /* EZBingRequest.m */; }; + 62D2E58B2B32ED9A0070D298 /* ALiTranslateType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 62D2E58A2B32ED9A0070D298 /* ALiTranslateType.swift */; }; + 62E5BABE2B32956700F5F728 /* ALiService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 62E5BABD2B32956700F5F728 /* ALiService.swift */; }; + 62E8E6E62B32F7EC005BFAD3 /* ALiResponse.swift in Sources */ = {isa = PBXBuildFile; fileRef = 62E8E6E52B32F7EC005BFAD3 /* ALiResponse.swift */; }; 62ED29A22B15F1F500901F51 /* EZWrapView.m in Sources */ = {isa = PBXBuildFile; fileRef = 62ED29A12B15F1F500901F51 /* EZWrapView.m */; }; 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 */; }; @@ -666,6 +669,9 @@ 6295DE332A84EF76006145F4 /* EZBingLookupModel.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = EZBingLookupModel.m; sourceTree = ""; }; 62A2D03D2A82967F007EEB01 /* EZBingRequest.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = EZBingRequest.h; sourceTree = ""; }; 62A2D03E2A82967F007EEB01 /* EZBingRequest.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = EZBingRequest.m; sourceTree = ""; }; + 62D2E58A2B32ED9A0070D298 /* ALiTranslateType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ALiTranslateType.swift; sourceTree = ""; }; + 62E5BABD2B32956700F5F728 /* ALiService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ALiService.swift; sourceTree = ""; }; + 62E8E6E52B32F7EC005BFAD3 /* ALiResponse.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ALiResponse.swift; sourceTree = ""; }; 62ED29A02B15F1F500901F51 /* EZWrapView.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = EZWrapView.h; sourceTree = ""; }; 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; }; @@ -1176,6 +1182,7 @@ 03B0222B29231FA6001C7E63 /* Service */ = { isa = PBXGroup; children = ( + 62E5BABC2B32952B00F5F728 /* ALi */, 17BCAEF22B0DFF9000A7D372 /* Niutrans */, 2746AEBF2AF95040005FE0A1 /* Caiyun */, C4DD01E72B12B3B00025EE8E /* Tencent */, @@ -1911,6 +1918,16 @@ path = Bing; sourceTree = ""; }; + 62E5BABC2B32952B00F5F728 /* ALi */ = { + isa = PBXGroup; + children = ( + 62E5BABD2B32956700F5F728 /* ALiService.swift */, + 62D2E58A2B32ED9A0070D298 /* ALiTranslateType.swift */, + 62E8E6E52B32F7EC005BFAD3 /* ALiResponse.swift */, + ); + path = ALi; + sourceTree = ""; + }; 62ED299F2B15F1BE00901F51 /* EZWrapView */ = { isa = PBXGroup; children = ( @@ -2351,6 +2368,7 @@ 033363A6293C4AFA00FED9C8 /* PrintBeautifulLog.m in Sources */, 039CC914292FB3180037B91E /* EZPopUpButton.m in Sources */, 0399C6B829A9F4B800B4AFCC /* EZSchemeParser.m in Sources */, + 62E8E6E62B32F7EC005BFAD3 /* ALiResponse.swift in Sources */, 03542A3A2937AE6400C34C33 /* EZQueryService.m in Sources */, 03B0230529231FA6001C7E63 /* EZButton.m in Sources */, 03B0232329231FA6001C7E63 /* NSString+MM.m in Sources */, @@ -2370,6 +2388,7 @@ 0361967B2A0037F700806370 /* NSData+EZMD5.m in Sources */, 03BFFC68295F4B87004E033E /* EZYoudaoDictModel.m in Sources */, 03247E3A296AE8EC00AFCD67 /* EZLoadingAnimationView.m in Sources */, + 62E5BABE2B32956700F5F728 /* ALiService.swift in Sources */, 0396D615292CC4C3006A11D9 /* EZLocalStorage.m in Sources */, 0329CD6F29EE924500963F78 /* EZRightClickDetector.m in Sources */, 033C30FC2A7409C40095926A /* TTTDictionary.m in Sources */, @@ -2439,6 +2458,7 @@ 03B0231429231FA6001C7E63 /* DarkModeManager.m in Sources */, 03BDA7C02A26DA280079D04F /* XPMArgumentPackage.m in Sources */, 2746AEC12AF95138005FE0A1 /* CaiyunService.swift in Sources */, + 62D2E58B2B32ED9A0070D298 /* ALiTranslateType.swift in Sources */, 037852B02957FEB200D0E2CF /* EZServiceViewController.m in Sources */, 6220AD5B2A82812300BBFB52 /* EZBingService.m in Sources */, 039F5508294B6E29004AB940 /* EZAboutViewController.m in Sources */, diff --git a/Easydict/App/Easydict-Bridging-Header.h b/Easydict/App/Easydict-Bridging-Header.h index 3914d5650..d422322f2 100644 --- a/Easydict/App/Easydict-Bridging-Header.h +++ b/Easydict/App/Easydict-Bridging-Header.h @@ -8,3 +8,4 @@ #import "EZConstKey.h" #import "NSString+EZChineseText.h" #import "EZError.h" +#import "NSString+EZConvenience.h" diff --git a/Easydict/App/Localizable.xcstrings b/Easydict/App/Localizable.xcstrings index 6f8004469..faae2703e 100644 --- a/Easydict/App/Localizable.xcstrings +++ b/Easydict/App/Localizable.xcstrings @@ -34,6 +34,23 @@ } } }, + "ali_translate" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "ALi Translate" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "阿里翻译" + } + } + } + }, "Alignment Error" : { "comment" : "Error description", "localizations" : { diff --git a/Easydict/Feature/Service/ALi/ALiResponse.swift b/Easydict/Feature/Service/ALi/ALiResponse.swift new file mode 100644 index 000000000..34c2e21c9 --- /dev/null +++ b/Easydict/Feature/Service/ALi/ALiResponse.swift @@ -0,0 +1,37 @@ +// +// ALiResponse.swift +// Easydict +// +// Created by choykarl on 2023/12/20. +// Copyright © 2023 izual. All rights reserved. +// + +import Foundation + +/** + { + "requestId": "64AB21D8-B14C-4E53-8B78-55ACAC370F9A", + "success": true, + "httpStatusCode": 200, + "code": "", + "message": "", + "data": { + "translateText": "你好", + "detectLanguage": "en" + } + } + */ + +struct ALiResponse: Codable { + struct Data: Codable { + var translateText: String? + var detectLanguage: String? + } + + var requestId: String? + var success: Bool? + var httpStatusCode: Int? + var code: String? + var message: String? + var data: Data? +} diff --git a/Easydict/Feature/Service/ALi/ALiService.swift b/Easydict/Feature/Service/ALi/ALiService.swift new file mode 100644 index 000000000..56bbb0e73 --- /dev/null +++ b/Easydict/Feature/Service/ALi/ALiService.swift @@ -0,0 +1,97 @@ +// +// ALiService.swift +// Easydict +// +// Created by choykarl on 2023/12/20. +// Copyright © 2023 izual. All rights reserved. +// + +import Alamofire +import Foundation + +@objc(EZALiService) +class ALiService: QueryService { + override func serviceType() -> ServiceType { + .ali + } + + override public func link() -> String? { + "https://translate.alibaba.com/" + } + + override public func name() -> String { + NSLocalizedString("ali_translate", comment: "The name of Ali Translate") + } + + override public func supportLanguagesDictionary() -> MMOrderedDictionary { + // TODO: Replace MMOrderedDictionary in the API + let orderedDict = MMOrderedDictionary() + ALiTranslateType.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("ali Translate does not support OCR") + throw QueryServiceError.notSupported + } + + override func translate(_ text: String, from: Language, to: Language, completion: @escaping (EZQueryResult, Error?) -> Void) { + let transType = ALiTranslateType.transType(from: from, to: to) + guard transType != .unsupported else { + let showingFrom = EZLanguageManager.shared().showingLanguageName(from) + let showingTo = EZLanguageManager.shared().showingLanguageName(to) + let error = EZError(type: .unsupportedLanguage, description: "\(showingFrom) --> \(showingTo)") + completion(result, error) + return + } + + let request = AF.request("https://translate.alibaba.com/api/translate/text", + method: .get, + parameters: [ + "srcLang": transType.sourceLanguage, + "tgtLang": transType.targetLanguage, + "domain": "general", + "query": text, + ]) + .validate() + .responseDecodable(of: ALiResponse.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 + if let data = value.data, let translateText = data.translateText { + result.translatedResults = [translateText] + completion(result, nil) + } else { + let ezError = EZError(type: value.httpStatusCode == 500 ? .unsupportedLanguage : .noResultsFound) + ezError.errorDataMessage = value.message ?? "ali translate failed" + completion(result, ezError) + } + + case let .failure(error): + NSLog("ali lookup error \(error)") + let ezError = EZError(nsError: error) + + if let data = response.data { + do { + let errorResponse = try JSONDecoder().decode(TencentErrorResponse.self, from: data) + ezError?.errorDataMessage = errorResponse.response.error.message + } catch { + NSLog("Failed to decode error response: \(error)") + } + } + completion(result, ezError) + } + } + + queryModel.setStop({ + request.cancel() + }, serviceType: serviceType().rawValue) + } +} diff --git a/Easydict/Feature/Service/ALi/ALiTranslateType.swift b/Easydict/Feature/Service/ALi/ALiTranslateType.swift new file mode 100644 index 000000000..c80c2eecf --- /dev/null +++ b/Easydict/Feature/Service/ALi/ALiTranslateType.swift @@ -0,0 +1,89 @@ +// +// ALiTranslateType.swift +// Easydict +// +// Created by choykarl on 2023/12/20. +// Copyright © 2023 izual. All rights reserved. +// + +import Foundation + +struct ALiTranslateType: Equatable { + var sourceLanguage: String + var targetLanguage: String + + static let unsupported = ALiTranslateType(sourceLanguage: "unsupported", targetLanguage: "unsupported") + + /// https://help.aliyun.com/document_detail/215387.html?spm=a2c4g.158269.0.0.48d54b8ab1Jeol#h2-url-1 + static let supportLanguagesDictionary: [Language: String] = [ + .auto: "auto", + .simplifiedChinese: "zh", + .traditionalChinese: "zh-tw", + .english: "en", + .japanese: "ja", + .korean: "ko", + .french: "fr", + .spanish: "es", + .portuguese: "pt", + .italian: "it", + .german: "de", + .russian: "ru", + .arabic: "ar", + .swedish: "sv", + .romanian: "ro", + .thai: "th", + .slovak: "sk", + .dutch: "nl", + .hungarian: "hu", + .greek: "el", + .danish: "da", + .finnish: "fi", + .polish: "pl", + .czech: "cs", + .turkish: "tr", + .lithuanian: "lt", + .latvian: "lv", + .bulgarian: "bg", + .malay: "ms", + .slovenian: "sl", + .estonian: "et", + .vietnamese: "vi", + .persian: "fa", + .hindi: "hi", + .telugu: "te", + .tamil: "ta", + .urdu: "ur", + .filipino: "fil", + .khmer: "km", + .lao: "lo", + .bengali: "bn", + .burmese: "my", + .norwegian: "no", + .croatian: "hbs", + .mongolian: "mn", + .hebrew: "he", + ] + + static func transType(from: Language, to: Language) -> ALiTranslateType { + // !!!: Tencent translate support traditionalChinese as target language if target languages contain simplifiedChinese. + + /** + 文本翻译除繁体中文、蒙语、粤语外,其他212种语言,可支持任意两种语言之间互译。繁体中文、蒙语、粤语仅支持与中文之间的互译。文本翻译支持源语言的自动语言检测,语言代码为auto(粤语为源语言时,不支持使用auto作为语言代码)。 + */ + if from == .traditionalChinese || from == .mongolian, to != .simplifiedChinese { + return .unsupported + } else if to == .traditionalChinese, from != .simplifiedChinese { + return .unsupported + } else if to == .mongolian, from != .simplifiedChinese { + return .unsupported + } + + guard let fromLanguage = supportLanguagesDictionary[from], + let toLanguage = supportLanguagesDictionary[to] + else { + return .unsupported + } + + return ALiTranslateType(sourceLanguage: fromLanguage, targetLanguage: toLanguage) + } +} diff --git a/Easydict/Feature/Service/Model/EZEnumTypes.h b/Easydict/Feature/Service/Model/EZEnumTypes.h index 75ee92999..28dbeea3a 100644 --- a/Easydict/Feature/Service/Model/EZEnumTypes.h +++ b/Easydict/Feature/Service/Model/EZEnumTypes.h @@ -42,6 +42,7 @@ FOUNDATION_EXPORT EZServiceType const EZServiceTypeBing; FOUNDATION_EXPORT EZServiceType const EZServiceTypeNiuTrans; FOUNDATION_EXPORT EZServiceType const EZServiceTypeCaiyun; FOUNDATION_EXPORT EZServiceType const EZServiceTypeTencent; +FOUNDATION_EXPORT EZServiceType const EZServiceTypeAli; 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 9ca759786..76e778da2 100644 --- a/Easydict/Feature/Service/Model/EZEnumTypes.m +++ b/Easydict/Feature/Service/Model/EZEnumTypes.m @@ -21,6 +21,7 @@ NSString *const EZServiceTypeNiuTrans = @"NiuTrans"; NSString *const EZServiceTypeCaiyun = @"Caiyun"; NSString *const EZServiceTypeTencent = @"Tencent"; +NSString *const EZServiceTypeAli = @"Alibaba"; NSString *const EZServiceTypeAppleDictionary = @"AppleDictionary"; diff --git a/Easydict/Feature/Service/Model/EZServiceTypes.m b/Easydict/Feature/Service/Model/EZServiceTypes.m index a1cc9a103..88d507019 100644 --- a/Easydict/Feature/Service/Model/EZServiceTypes.m +++ b/Easydict/Feature/Service/Model/EZServiceTypes.m @@ -62,6 +62,7 @@ + (instancetype)allocWithZone:(struct _NSZone *)zone { EZServiceTypeNiuTrans, [EZNiuTransTranslate class], EZServiceTypeCaiyun, [EZCaiyunService class], EZServiceTypeTencent, [EZTencentService class], + EZServiceTypeAli, [EZALiService class], nil]; return allServiceDict; } diff --git a/Easydict/Feature/Utility/EZCategory/NSString/NSString+EZConvenience.h b/Easydict/Feature/Utility/EZCategory/NSString/NSString+EZConvenience.h index 46e19688e..e1846262e 100644 --- a/Easydict/Feature/Utility/EZCategory/NSString/NSString+EZConvenience.h +++ b/Easydict/Feature/Utility/EZCategory/NSString/NSString+EZConvenience.h @@ -40,6 +40,8 @@ NS_ASSUME_NONNULL_BEGIN /// Replace \" with " - (NSString *)escapedXMLString; +- (NSString *)unescapedXMLString; + - (void)copyToPasteboard; - (void)copyToPasteboardSafely; From 3de6bd71d0611e981a71d5f8718bc79d923816bd Mon Sep 17 00:00:00 2001 From: choykarl <253440030@qq.com> Date: Thu, 21 Dec 2023 09:45:06 +0800 Subject: [PATCH 02/16] rename --- Easydict.xcodeproj/project.pbxproj | 30 +++++++++--------- .../Alibaba.imageset/Contents.json | 21 ++++++++++++ .../Alibaba.imageset/ali translate.png | Bin 0 -> 946 bytes .../AliResponse.swift} | 4 +-- .../ALiService.swift => Ali/AliService.swift} | 12 +++---- .../AliTranslateType.swift} | 12 +++---- .../Feature/Service/Model/EZServiceTypes.m | 2 +- 7 files changed, 50 insertions(+), 31 deletions(-) create mode 100644 Easydict/App/Assets.xcassets/service-icon/Alibaba.imageset/Contents.json create mode 100644 Easydict/App/Assets.xcassets/service-icon/Alibaba.imageset/ali translate.png rename Easydict/Feature/Service/{ALi/ALiResponse.swift => Ali/AliResponse.swift} (92%) rename Easydict/Feature/Service/{ALi/ALiService.swift => Ali/AliService.swift} (94%) rename Easydict/Feature/Service/{ALi/ALiTranslateType.swift => Ali/AliTranslateType.swift} (86%) diff --git a/Easydict.xcodeproj/project.pbxproj b/Easydict.xcodeproj/project.pbxproj index 18e114f0e..04fec5587 100644 --- a/Easydict.xcodeproj/project.pbxproj +++ b/Easydict.xcodeproj/project.pbxproj @@ -223,9 +223,9 @@ 6295DE312A84D82E006145F4 /* EZBingTranslateModel.m in Sources */ = {isa = PBXBuildFile; fileRef = 6295DE302A84D82E006145F4 /* EZBingTranslateModel.m */; }; 6295DE342A84EF76006145F4 /* EZBingLookupModel.m in Sources */ = {isa = PBXBuildFile; fileRef = 6295DE332A84EF76006145F4 /* EZBingLookupModel.m */; }; 62A2D03F2A82967F007EEB01 /* EZBingRequest.m in Sources */ = {isa = PBXBuildFile; fileRef = 62A2D03E2A82967F007EEB01 /* EZBingRequest.m */; }; - 62D2E58B2B32ED9A0070D298 /* ALiTranslateType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 62D2E58A2B32ED9A0070D298 /* ALiTranslateType.swift */; }; - 62E5BABE2B32956700F5F728 /* ALiService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 62E5BABD2B32956700F5F728 /* ALiService.swift */; }; - 62E8E6E62B32F7EC005BFAD3 /* ALiResponse.swift in Sources */ = {isa = PBXBuildFile; fileRef = 62E8E6E52B32F7EC005BFAD3 /* ALiResponse.swift */; }; + 62D2E58B2B32ED9A0070D298 /* AliTranslateType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 62D2E58A2B32ED9A0070D298 /* AliTranslateType.swift */; }; + 62E5BABE2B32956700F5F728 /* AliService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 62E5BABD2B32956700F5F728 /* AliService.swift */; }; + 62E8E6E62B32F7EC005BFAD3 /* AliResponse.swift in Sources */ = {isa = PBXBuildFile; fileRef = 62E8E6E52B32F7EC005BFAD3 /* AliResponse.swift */; }; 62ED29A22B15F1F500901F51 /* EZWrapView.m in Sources */ = {isa = PBXBuildFile; fileRef = 62ED29A12B15F1F500901F51 /* EZWrapView.m */; }; 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 */; }; @@ -669,9 +669,9 @@ 6295DE332A84EF76006145F4 /* EZBingLookupModel.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = EZBingLookupModel.m; sourceTree = ""; }; 62A2D03D2A82967F007EEB01 /* EZBingRequest.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = EZBingRequest.h; sourceTree = ""; }; 62A2D03E2A82967F007EEB01 /* EZBingRequest.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = EZBingRequest.m; sourceTree = ""; }; - 62D2E58A2B32ED9A0070D298 /* ALiTranslateType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ALiTranslateType.swift; sourceTree = ""; }; - 62E5BABD2B32956700F5F728 /* ALiService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ALiService.swift; sourceTree = ""; }; - 62E8E6E52B32F7EC005BFAD3 /* ALiResponse.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ALiResponse.swift; sourceTree = ""; }; + 62D2E58A2B32ED9A0070D298 /* AliTranslateType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AliTranslateType.swift; sourceTree = ""; }; + 62E5BABD2B32956700F5F728 /* AliService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AliService.swift; sourceTree = ""; }; + 62E8E6E52B32F7EC005BFAD3 /* AliResponse.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AliResponse.swift; sourceTree = ""; }; 62ED29A02B15F1F500901F51 /* EZWrapView.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = EZWrapView.h; sourceTree = ""; }; 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; }; @@ -1182,7 +1182,7 @@ 03B0222B29231FA6001C7E63 /* Service */ = { isa = PBXGroup; children = ( - 62E5BABC2B32952B00F5F728 /* ALi */, + 62E5BABC2B32952B00F5F728 /* Ali */, 17BCAEF22B0DFF9000A7D372 /* Niutrans */, 2746AEBF2AF95040005FE0A1 /* Caiyun */, C4DD01E72B12B3B00025EE8E /* Tencent */, @@ -1918,14 +1918,14 @@ path = Bing; sourceTree = ""; }; - 62E5BABC2B32952B00F5F728 /* ALi */ = { + 62E5BABC2B32952B00F5F728 /* Ali */ = { isa = PBXGroup; children = ( - 62E5BABD2B32956700F5F728 /* ALiService.swift */, - 62D2E58A2B32ED9A0070D298 /* ALiTranslateType.swift */, - 62E8E6E52B32F7EC005BFAD3 /* ALiResponse.swift */, + 62E5BABD2B32956700F5F728 /* AliService.swift */, + 62D2E58A2B32ED9A0070D298 /* AliTranslateType.swift */, + 62E8E6E52B32F7EC005BFAD3 /* AliResponse.swift */, ); - path = ALi; + path = Ali; sourceTree = ""; }; 62ED299F2B15F1BE00901F51 /* EZWrapView */ = { @@ -2368,7 +2368,7 @@ 033363A6293C4AFA00FED9C8 /* PrintBeautifulLog.m in Sources */, 039CC914292FB3180037B91E /* EZPopUpButton.m in Sources */, 0399C6B829A9F4B800B4AFCC /* EZSchemeParser.m in Sources */, - 62E8E6E62B32F7EC005BFAD3 /* ALiResponse.swift in Sources */, + 62E8E6E62B32F7EC005BFAD3 /* AliResponse.swift in Sources */, 03542A3A2937AE6400C34C33 /* EZQueryService.m in Sources */, 03B0230529231FA6001C7E63 /* EZButton.m in Sources */, 03B0232329231FA6001C7E63 /* NSString+MM.m in Sources */, @@ -2388,7 +2388,7 @@ 0361967B2A0037F700806370 /* NSData+EZMD5.m in Sources */, 03BFFC68295F4B87004E033E /* EZYoudaoDictModel.m in Sources */, 03247E3A296AE8EC00AFCD67 /* EZLoadingAnimationView.m in Sources */, - 62E5BABE2B32956700F5F728 /* ALiService.swift in Sources */, + 62E5BABE2B32956700F5F728 /* AliService.swift in Sources */, 0396D615292CC4C3006A11D9 /* EZLocalStorage.m in Sources */, 0329CD6F29EE924500963F78 /* EZRightClickDetector.m in Sources */, 033C30FC2A7409C40095926A /* TTTDictionary.m in Sources */, @@ -2458,7 +2458,7 @@ 03B0231429231FA6001C7E63 /* DarkModeManager.m in Sources */, 03BDA7C02A26DA280079D04F /* XPMArgumentPackage.m in Sources */, 2746AEC12AF95138005FE0A1 /* CaiyunService.swift in Sources */, - 62D2E58B2B32ED9A0070D298 /* ALiTranslateType.swift in Sources */, + 62D2E58B2B32ED9A0070D298 /* AliTranslateType.swift in Sources */, 037852B02957FEB200D0E2CF /* EZServiceViewController.m in Sources */, 6220AD5B2A82812300BBFB52 /* EZBingService.m in Sources */, 039F5508294B6E29004AB940 /* EZAboutViewController.m in Sources */, diff --git a/Easydict/App/Assets.xcassets/service-icon/Alibaba.imageset/Contents.json b/Easydict/App/Assets.xcassets/service-icon/Alibaba.imageset/Contents.json new file mode 100644 index 000000000..45e59a56c --- /dev/null +++ b/Easydict/App/Assets.xcassets/service-icon/Alibaba.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "ali translate.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Easydict/App/Assets.xcassets/service-icon/Alibaba.imageset/ali translate.png b/Easydict/App/Assets.xcassets/service-icon/Alibaba.imageset/ali translate.png new file mode 100644 index 0000000000000000000000000000000000000000..78b6286233311764fe1fa9988cafaf16729f148f GIT binary patch literal 946 zcmV;j15NyiP)Px#Fi=cXMMrQ>QD zT3pszT-9A(*jilGT3ppxT-8}y)Ix{*=>Px#4|GyaQvmir80QMrQ^93>Dvq739uKkp z00098Nkl+tWhF=Ye#2~za3o?3b z$oFLkQ=_{TA!XR&9=b`ZkA;FZgg_Ttusz<}v>)(d0SJff>ktGVv7xZNTbFNL z6GCW0HdK0Nu3}1QL-~0Q*zh1aw~KYjh%f}$oI16OwkYcH$TO1jmjmW__t~vzh!9Ss zQR_|e#;-1iiHbKK)>nu!F3RY7=|jDAe0Hqk(#gzzzdBOJ3j z;43nO<@CxeW`kn~&kHTFJ#p-38EZNSmCrzvYMEP}AY00M03!65kjOf> zi&z!95JD9>NFtGY+%9|qykidj+y;`!GPesh)^KSD5K;$8B+{Rt*FF^?xIKa;vTz4k z_y|CFN2gBEo~~t>;s5{u07*qoM6N<$f*U`jg#Z8m literal 0 HcmV?d00001 diff --git a/Easydict/Feature/Service/ALi/ALiResponse.swift b/Easydict/Feature/Service/Ali/AliResponse.swift similarity index 92% rename from Easydict/Feature/Service/ALi/ALiResponse.swift rename to Easydict/Feature/Service/Ali/AliResponse.swift index 34c2e21c9..716dd2c00 100644 --- a/Easydict/Feature/Service/ALi/ALiResponse.swift +++ b/Easydict/Feature/Service/Ali/AliResponse.swift @@ -1,5 +1,5 @@ // -// ALiResponse.swift +// AliResponse.swift // Easydict // // Created by choykarl on 2023/12/20. @@ -22,7 +22,7 @@ import Foundation } */ -struct ALiResponse: Codable { +struct AliResponse: Codable { struct Data: Codable { var translateText: String? var detectLanguage: String? diff --git a/Easydict/Feature/Service/ALi/ALiService.swift b/Easydict/Feature/Service/Ali/AliService.swift similarity index 94% rename from Easydict/Feature/Service/ALi/ALiService.swift rename to Easydict/Feature/Service/Ali/AliService.swift index 56bbb0e73..b8ecd3cde 100644 --- a/Easydict/Feature/Service/ALi/ALiService.swift +++ b/Easydict/Feature/Service/Ali/AliService.swift @@ -1,5 +1,5 @@ // -// ALiService.swift +// AliService.swift // Easydict // // Created by choykarl on 2023/12/20. @@ -9,8 +9,8 @@ import Alamofire import Foundation -@objc(EZALiService) -class ALiService: QueryService { +@objc(EZAliService) +class AliService: QueryService { override func serviceType() -> ServiceType { .ali } @@ -26,7 +26,7 @@ class ALiService: QueryService { override public func supportLanguagesDictionary() -> MMOrderedDictionary { // TODO: Replace MMOrderedDictionary in the API let orderedDict = MMOrderedDictionary() - ALiTranslateType.supportLanguagesDictionary.forEach { key, value in + AliTranslateType.supportLanguagesDictionary.forEach { key, value in orderedDict.setObject(value as NSString, forKey: key.rawValue as NSString) } return orderedDict @@ -38,7 +38,7 @@ class ALiService: QueryService { } override func translate(_ text: String, from: Language, to: Language, completion: @escaping (EZQueryResult, Error?) -> Void) { - let transType = ALiTranslateType.transType(from: from, to: to) + let transType = AliTranslateType.transType(from: from, to: to) guard transType != .unsupported else { let showingFrom = EZLanguageManager.shared().showingLanguageName(from) let showingTo = EZLanguageManager.shared().showingLanguageName(to) @@ -56,7 +56,7 @@ class ALiService: QueryService { "query": text, ]) .validate() - .responseDecodable(of: ALiResponse.self) { [weak self] response in + .responseDecodable(of: AliResponse.self) { [weak self] response in guard let self else { return } let result = self.result diff --git a/Easydict/Feature/Service/ALi/ALiTranslateType.swift b/Easydict/Feature/Service/Ali/AliTranslateType.swift similarity index 86% rename from Easydict/Feature/Service/ALi/ALiTranslateType.swift rename to Easydict/Feature/Service/Ali/AliTranslateType.swift index c80c2eecf..3227eda91 100644 --- a/Easydict/Feature/Service/ALi/ALiTranslateType.swift +++ b/Easydict/Feature/Service/Ali/AliTranslateType.swift @@ -1,5 +1,5 @@ // -// ALiTranslateType.swift +// AliTranslateType.swift // Easydict // // Created by choykarl on 2023/12/20. @@ -8,11 +8,11 @@ import Foundation -struct ALiTranslateType: Equatable { +struct AliTranslateType: Equatable { var sourceLanguage: String var targetLanguage: String - static let unsupported = ALiTranslateType(sourceLanguage: "unsupported", targetLanguage: "unsupported") + static let unsupported = AliTranslateType(sourceLanguage: "unsupported", targetLanguage: "unsupported") /// https://help.aliyun.com/document_detail/215387.html?spm=a2c4g.158269.0.0.48d54b8ab1Jeol#h2-url-1 static let supportLanguagesDictionary: [Language: String] = [ @@ -64,9 +64,7 @@ struct ALiTranslateType: Equatable { .hebrew: "he", ] - static func transType(from: Language, to: Language) -> ALiTranslateType { - // !!!: Tencent translate support traditionalChinese as target language if target languages contain simplifiedChinese. - + static func transType(from: Language, to: Language) -> AliTranslateType { /** 文本翻译除繁体中文、蒙语、粤语外,其他212种语言,可支持任意两种语言之间互译。繁体中文、蒙语、粤语仅支持与中文之间的互译。文本翻译支持源语言的自动语言检测,语言代码为auto(粤语为源语言时,不支持使用auto作为语言代码)。 */ @@ -84,6 +82,6 @@ struct ALiTranslateType: Equatable { return .unsupported } - return ALiTranslateType(sourceLanguage: fromLanguage, targetLanguage: toLanguage) + return AliTranslateType(sourceLanguage: fromLanguage, targetLanguage: toLanguage) } } diff --git a/Easydict/Feature/Service/Model/EZServiceTypes.m b/Easydict/Feature/Service/Model/EZServiceTypes.m index 88d507019..d63ed6649 100644 --- a/Easydict/Feature/Service/Model/EZServiceTypes.m +++ b/Easydict/Feature/Service/Model/EZServiceTypes.m @@ -62,7 +62,7 @@ + (instancetype)allocWithZone:(struct _NSZone *)zone { EZServiceTypeNiuTrans, [EZNiuTransTranslate class], EZServiceTypeCaiyun, [EZCaiyunService class], EZServiceTypeTencent, [EZTencentService class], - EZServiceTypeAli, [EZALiService class], + EZServiceTypeAli, [EZAliService class], nil]; return allServiceDict; } From 0c7149697782fe21e22531494f6a4d65b4a8e130 Mon Sep 17 00:00:00 2001 From: choykarl <253440030@qq.com> Date: Thu, 21 Dec 2023 10:02:35 +0800 Subject: [PATCH 03/16] convert html keyword --- Easydict/App/Easydict-Bridging-Header.h | 1 + Easydict/Feature/Service/Ali/AliService.swift | 3 +-- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Easydict/App/Easydict-Bridging-Header.h b/Easydict/App/Easydict-Bridging-Header.h index d422322f2..fb6c13f1b 100644 --- a/Easydict/App/Easydict-Bridging-Header.h +++ b/Easydict/App/Easydict-Bridging-Header.h @@ -9,3 +9,4 @@ #import "NSString+EZChineseText.h" #import "EZError.h" #import "NSString+EZConvenience.h" +#import "NSString+EZConvenience.h" diff --git a/Easydict/Feature/Service/Ali/AliService.swift b/Easydict/Feature/Service/Ali/AliService.swift index b8ecd3cde..d442cdc85 100644 --- a/Easydict/Feature/Service/Ali/AliService.swift +++ b/Easydict/Feature/Service/Ali/AliService.swift @@ -66,14 +66,13 @@ class AliService: QueryService { result.to = to result.queryText = text if let data = value.data, let translateText = data.translateText { - result.translatedResults = [translateText] + result.translatedResults = [translateText.unescapedXML()] completion(result, nil) } else { let ezError = EZError(type: value.httpStatusCode == 500 ? .unsupportedLanguage : .noResultsFound) ezError.errorDataMessage = value.message ?? "ali translate failed" completion(result, ezError) } - case let .failure(error): NSLog("ali lookup error \(error)") let ezError = EZError(nsError: error) From 531d826cd33406ca45c36980a2804532108880a4 Mon Sep 17 00:00:00 2001 From: choykarl <253440030@qq.com> Date: Thu, 21 Dec 2023 10:06:16 +0800 Subject: [PATCH 04/16] remove redundant code --- Easydict/App/Easydict-Bridging-Header.h | 1 - 1 file changed, 1 deletion(-) diff --git a/Easydict/App/Easydict-Bridging-Header.h b/Easydict/App/Easydict-Bridging-Header.h index fb6c13f1b..d422322f2 100644 --- a/Easydict/App/Easydict-Bridging-Header.h +++ b/Easydict/App/Easydict-Bridging-Header.h @@ -9,4 +9,3 @@ #import "NSString+EZChineseText.h" #import "EZError.h" #import "NSString+EZConvenience.h" -#import "NSString+EZConvenience.h" From 6c7694e276f1b3f4ecb3f8cb344b235c77b069de Mon Sep 17 00:00:00 2001 From: tisfeng Date: Thu, 21 Dec 2023 22:58:34 +0800 Subject: [PATCH 05/16] perf: improve Ali error message --- Easydict/Feature/Service/Ali/AliResponse.swift | 10 ++++++++++ Easydict/Feature/Service/Ali/AliService.swift | 3 +-- 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/Easydict/Feature/Service/Ali/AliResponse.swift b/Easydict/Feature/Service/Ali/AliResponse.swift index 716dd2c00..a7a1ab737 100644 --- a/Easydict/Feature/Service/Ali/AliResponse.swift +++ b/Easydict/Feature/Service/Ali/AliResponse.swift @@ -20,6 +20,16 @@ import Foundation "detectLanguage": "en" } } + + error: + { + "requestId": "877D2097-6FE4-4B24-BAF1-41AB561C1E67", + "success": false, + "httpStatusCode": 500, + "code": "ParamError", + "message": "Query length limit exceeded", + "data": null + } */ struct AliResponse: Codable { diff --git a/Easydict/Feature/Service/Ali/AliService.swift b/Easydict/Feature/Service/Ali/AliService.swift index d442cdc85..6cce43514 100644 --- a/Easydict/Feature/Service/Ali/AliService.swift +++ b/Easydict/Feature/Service/Ali/AliService.swift @@ -69,8 +69,7 @@ class AliService: QueryService { result.translatedResults = [translateText.unescapedXML()] completion(result, nil) } else { - let ezError = EZError(type: value.httpStatusCode == 500 ? .unsupportedLanguage : .noResultsFound) - ezError.errorDataMessage = value.message ?? "ali translate failed" + let ezError = EZError(type: .API, description: value.code, errorDataMessage: value.message) completion(result, ezError) } case let .failure(error): From 94cf2e0dcb395dd409a6a3ebb47a5d254ea596f9 Mon Sep 17 00:00:00 2001 From: tisfeng Date: Fri, 22 Dec 2023 00:43:00 +0800 Subject: [PATCH 06/16] perf: make Ali support traditional Chinese --- Easydict/Feature/Service/Ali/AliService.swift | 8 ++++ .../Service/Ali/AliTranslateType.swift | 44 +++++++++++++------ .../Service/Language/EZLanguageModel.h | 2 + .../Service/Language/EZLanguageModel.m | 3 ++ .../Tencent/TencentTranslateType.swift | 4 +- 5 files changed, 45 insertions(+), 16 deletions(-) diff --git a/Easydict/Feature/Service/Ali/AliService.swift b/Easydict/Feature/Service/Ali/AliService.swift index 6cce43514..e1a77b9b7 100644 --- a/Easydict/Feature/Service/Ali/AliService.swift +++ b/Easydict/Feature/Service/Ali/AliService.swift @@ -37,6 +37,14 @@ class AliService: QueryService { throw QueryServiceError.notSupported } + override public func autoConvertTraditionalChinese() -> Bool { + // If translate traditionalChinese <--> simplifiedChinese, use Ali API directly. + if EZLanguageManager.shared().onlyContainsChineseLanguages([queryModel.queryFromLanguage, queryModel.queryTargetLanguage]) { + return false + } + return true + } + override func translate(_ text: String, from: Language, to: Language, completion: @escaping (EZQueryResult, Error?) -> Void) { let transType = AliTranslateType.transType(from: from, to: to) guard transType != .unsupported else { diff --git a/Easydict/Feature/Service/Ali/AliTranslateType.swift b/Easydict/Feature/Service/Ali/AliTranslateType.swift index 3227eda91..ccf0cbf11 100644 --- a/Easydict/Feature/Service/Ali/AliTranslateType.swift +++ b/Easydict/Feature/Service/Ali/AliTranslateType.swift @@ -11,14 +11,18 @@ import Foundation struct AliTranslateType: Equatable { var sourceLanguage: String var targetLanguage: String - + static let unsupported = AliTranslateType(sourceLanguage: "unsupported", targetLanguage: "unsupported") - - /// https://help.aliyun.com/document_detail/215387.html?spm=a2c4g.158269.0.0.48d54b8ab1Jeol#h2-url-1 + + /// https://help.aliyun.com/zh/machine-translation/support/supported-languages-and-codes#h2-url-1 static let supportLanguagesDictionary: [Language: String] = [ .auto: "auto", .simplifiedChinese: "zh", - .traditionalChinese: "zh-tw", + + /** + traditionalChinese code is "zh-tw", but Ali only support traditionalChinese <--> simplifiedChinese, so we convert traditionalChinese manually. + */ + .traditionalChinese: "zh", // "zh-tw" .english: "en", .japanese: "ja", .korean: "ko", @@ -63,25 +67,37 @@ struct AliTranslateType: Equatable { .mongolian: "mn", .hebrew: "he", ] - + static func transType(from: Language, to: Language) -> AliTranslateType { /** 文本翻译除繁体中文、蒙语、粤语外,其他212种语言,可支持任意两种语言之间互译。繁体中文、蒙语、粤语仅支持与中文之间的互译。文本翻译支持源语言的自动语言检测,语言代码为auto(粤语为源语言时,不支持使用auto作为语言代码)。 + + https://help.aliyun.com/zh/machine-translation/support/supported-languages-and-codes */ - if from == .traditionalChinese || from == .mongolian, to != .simplifiedChinese { - return .unsupported - } else if to == .traditionalChinese, from != .simplifiedChinese { - return .unsupported - } else if to == .mongolian, from != .simplifiedChinese { + + if from == .mongolian, !to.isKindOfChinese() || to == .mongolian, !from.isKindOfChinese() { return .unsupported } - - guard let fromLanguage = supportLanguagesDictionary[from], - let toLanguage = supportLanguagesDictionary[to] + + guard var fromLanguage = supportLanguagesDictionary[from], + var toLanguage = supportLanguagesDictionary[to] else { return .unsupported } - + + // If translate traditionalChinese <--> simplifiedChinese, use Ali API directly. + if EZLanguageManager.shared().onlyContainsChineseLanguages([from, to]) { + let traditionalLangaugeCode = "zh-tw" + + // Maybe traditionalChinese --> traditionalChinese + if from == .traditionalChinese { + fromLanguage = traditionalLangaugeCode + } + if to == .traditionalChinese { + toLanguage = traditionalLangaugeCode + } + } + return AliTranslateType(sourceLanguage: fromLanguage, targetLanguage: toLanguage) } } diff --git a/Easydict/Feature/Service/Language/EZLanguageModel.h b/Easydict/Feature/Service/Language/EZLanguageModel.h index 7152182a4..6401290f2 100644 --- a/Easydict/Feature/Service/Language/EZLanguageModel.h +++ b/Easydict/Feature/Service/Language/EZLanguageModel.h @@ -69,6 +69,8 @@ FOUNDATION_EXPORT EZLanguage const EZLanguageCroatian; FOUNDATION_EXPORT EZLanguage const EZLanguageMongolian; FOUNDATION_EXPORT EZLanguage const EZLanguageHebrew; +FOUNDATION_EXPORT EZLanguage const EZLanguageUnsupported; + @interface EZLanguageModel : NSObject @property (nonatomic, copy) NSString *chineseName; diff --git a/Easydict/Feature/Service/Language/EZLanguageModel.m b/Easydict/Feature/Service/Language/EZLanguageModel.m index 472072272..cf01f194d 100644 --- a/Easydict/Feature/Service/Language/EZLanguageModel.m +++ b/Easydict/Feature/Service/Language/EZLanguageModel.m @@ -59,6 +59,9 @@ NSString *const EZLanguageMongolian = @"Mongolian"; NSString *const EZLanguageHebrew = @"Hebrew"; +NSString *const EZLanguageUnsupported = @"unsupported"; + + @implementation EZLanguageModel // 目前总计支持 49 种语言:简体中文,繁体中文,文言文,英语,日语,韩语,法语,西班牙语,葡萄牙语,意大利语,德语,俄语,阿拉伯语,瑞典语,罗马尼亚语,泰语,斯洛伐克语,荷兰语,匈牙利语,希腊语,丹麦语,芬兰语,波兰语,捷克语,土耳其语,立陶宛语,拉脱维亚语,乌克兰语,保加利亚语,印尼语,马来语,斯洛文尼亚语,爱沙尼亚语,越南语,波斯语,印地语,泰卢固语,泰米尔语,乌尔都语,菲律宾语,高棉语,老挝语,孟加拉语,缅甸语,挪威语,塞尔维亚语,克罗地亚语,蒙古语,希伯来语。 diff --git a/Easydict/Feature/Service/Tencent/TencentTranslateType.swift b/Easydict/Feature/Service/Tencent/TencentTranslateType.swift index c7df83236..e82753154 100644 --- a/Easydict/Feature/Service/Tencent/TencentTranslateType.swift +++ b/Easydict/Feature/Service/Tencent/TencentTranslateType.swift @@ -77,14 +77,14 @@ struct TencentTranslateType: Equatable { } extension [Language] { - // Contains Chinese language + /// Contains Chinese language, func containsChinese() -> Bool { contains { $0.isKindOfChinese() } } } extension Language { - // Is kind of Chinese language + /// Is kind of Chinese language, means it is simplifiedChinese or traditionalChinese. func isKindOfChinese() -> Bool { self == .simplifiedChinese || self == .traditionalChinese } From bd953c4bf1008830f1767238c21b50f6c72e4104 Mon Sep 17 00:00:00 2001 From: tisfeng Date: Fri, 22 Dec 2023 10:14:17 +0800 Subject: [PATCH 07/16] fix: do not use TencentErrorResponse to decode Ali error --- Easydict/Feature/Service/Ali/AliService.swift | 11 +-------- .../Service/Ali/AliTranslateType.swift | 24 +++++++++---------- 2 files changed, 13 insertions(+), 22 deletions(-) diff --git a/Easydict/Feature/Service/Ali/AliService.swift b/Easydict/Feature/Service/Ali/AliService.swift index e1a77b9b7..93d6216f5 100644 --- a/Easydict/Feature/Service/Ali/AliService.swift +++ b/Easydict/Feature/Service/Ali/AliService.swift @@ -82,16 +82,7 @@ class AliService: QueryService { } case let .failure(error): NSLog("ali lookup error \(error)") - let ezError = EZError(nsError: error) - - if let data = response.data { - do { - let errorResponse = try JSONDecoder().decode(TencentErrorResponse.self, from: data) - ezError?.errorDataMessage = errorResponse.response.error.message - } catch { - NSLog("Failed to decode error response: \(error)") - } - } + let ezError = EZError(nsError: error, errorResponseData: response.data) completion(result, ezError) } } diff --git a/Easydict/Feature/Service/Ali/AliTranslateType.swift b/Easydict/Feature/Service/Ali/AliTranslateType.swift index ccf0cbf11..4960794e9 100644 --- a/Easydict/Feature/Service/Ali/AliTranslateType.swift +++ b/Easydict/Feature/Service/Ali/AliTranslateType.swift @@ -11,17 +11,17 @@ import Foundation struct AliTranslateType: Equatable { var sourceLanguage: String var targetLanguage: String - + static let unsupported = AliTranslateType(sourceLanguage: "unsupported", targetLanguage: "unsupported") - + /// https://help.aliyun.com/zh/machine-translation/support/supported-languages-and-codes#h2-url-1 static let supportLanguagesDictionary: [Language: String] = [ .auto: "auto", .simplifiedChinese: "zh", - + /** - traditionalChinese code is "zh-tw", but Ali only support traditionalChinese <--> simplifiedChinese, so we convert traditionalChinese manually. - */ + traditionalChinese code is "zh-tw", but Ali only support traditionalChinese <--> simplifiedChinese, so we convert traditionalChinese manually. + */ .traditionalChinese: "zh", // "zh-tw" .english: "en", .japanese: "ja", @@ -67,28 +67,28 @@ struct AliTranslateType: Equatable { .mongolian: "mn", .hebrew: "he", ] - + static func transType(from: Language, to: Language) -> AliTranslateType { /** 文本翻译除繁体中文、蒙语、粤语外,其他212种语言,可支持任意两种语言之间互译。繁体中文、蒙语、粤语仅支持与中文之间的互译。文本翻译支持源语言的自动语言检测,语言代码为auto(粤语为源语言时,不支持使用auto作为语言代码)。 - + https://help.aliyun.com/zh/machine-translation/support/supported-languages-and-codes */ - + if from == .mongolian, !to.isKindOfChinese() || to == .mongolian, !from.isKindOfChinese() { return .unsupported } - + guard var fromLanguage = supportLanguagesDictionary[from], var toLanguage = supportLanguagesDictionary[to] else { return .unsupported } - + // If translate traditionalChinese <--> simplifiedChinese, use Ali API directly. if EZLanguageManager.shared().onlyContainsChineseLanguages([from, to]) { let traditionalLangaugeCode = "zh-tw" - + // Maybe traditionalChinese --> traditionalChinese if from == .traditionalChinese { fromLanguage = traditionalLangaugeCode @@ -97,7 +97,7 @@ struct AliTranslateType: Equatable { toLanguage = traditionalLangaugeCode } } - + return AliTranslateType(sourceLanguage: fromLanguage, targetLanguage: toLanguage) } } From bcd5bd27258192179aa5a8197379da03d457e7bc Mon Sep 17 00:00:00 2001 From: choykarl <253440030@qq.com> Date: Fri, 22 Dec 2023 21:55:34 +0800 Subject: [PATCH 08/16] add token rquest --- .../Feature/Service/Ali/AliResponse.swift | 14 +++ Easydict/Feature/Service/Ali/AliService.swift | 115 +++++++++++++----- 2 files changed, 98 insertions(+), 31 deletions(-) diff --git a/Easydict/Feature/Service/Ali/AliResponse.swift b/Easydict/Feature/Service/Ali/AliResponse.swift index a7a1ab737..6a8220b1d 100644 --- a/Easydict/Feature/Service/Ali/AliResponse.swift +++ b/Easydict/Feature/Service/Ali/AliResponse.swift @@ -45,3 +45,17 @@ struct AliResponse: Codable { var message: String? var data: Data? } + +/** + { + "token": "edda7f08-d74d-43b1-b8ff-cc2fdf2b5507", + "parameterName": "_csrf", + "headerName": "X-XSRF-TOKEN_PROPERTY_ITEM" + } + */ + +struct AliTokenResponse: Codable { + var token: String? + var parameterName: String? + var headerName: String? +} diff --git a/Easydict/Feature/Service/Ali/AliService.swift b/Easydict/Feature/Service/Ali/AliService.swift index 93d6216f5..5c30dd36a 100644 --- a/Easydict/Feature/Service/Ali/AliService.swift +++ b/Easydict/Feature/Service/Ali/AliService.swift @@ -11,6 +11,9 @@ import Foundation @objc(EZAliService) class AliService: QueryService { + private(set) var tokenResponse: AliTokenResponse? + private(set) var canRetry = true + override func serviceType() -> ServiceType { .ali } @@ -33,7 +36,7 @@ class AliService: QueryService { } override public func ocr(_: EZQueryModel) async throws -> EZOCRResult { - NSLog("ali Translate does not support OCR") + print("ali Translate does not support OCR") throw QueryServiceError.notSupported } @@ -55,37 +58,87 @@ class AliService: QueryService { return } + if let token = tokenResponse?.token, let parameterName = tokenResponse?.parameterName, !token.isEmpty, !parameterName.isEmpty { + self.request(token: token, parameterName: parameterName, transType: transType, text: text, from: from, to: to, completion: completion) + return + } + + // get request token + let request = AF.request("https://translate.alibaba.com/api/translate/csrftoken", method: .get) + .validate() + .responseDecodable(of: AliTokenResponse.self) { [weak self] response in + guard let self else { return } + switch response.result { + case let .success(value): + tokenResponse = value + self.request(token: value.token, parameterName: value.parameterName, transType: transType, text: text, from: from, to: to, completion: completion) + case let .failure(error): + print("ali translate get token error: \(error)") + self.request(token: nil, parameterName: nil, transType: transType, text: text, from: from, to: to, completion: completion) + } + } + + queryModel.setStop({ + request.cancel() + }, serviceType: serviceType().rawValue) + } + + /// If there is a token, use the POST method and request with the token as a parameter; otherwise, use the GET method to request. + private func request(token: String?, parameterName: String?, transType: AliTranslateType, text: String, from: Language, to: Language, completion: @escaping (EZQueryResult, Error?) -> Void) { + var hasToken = false + var parameters = [ + "srcLang": transType.sourceLanguage, + "tgtLang": transType.targetLanguage, + "domain": "general", + "query": text, + ] + + if let token, let parameterName, !token.isEmpty, !parameterName.isEmpty { + hasToken = true + parameters[parameterName] = token + } + let request = AF.request("https://translate.alibaba.com/api/translate/text", - method: .get, - parameters: [ - "srcLang": transType.sourceLanguage, - "tgtLang": transType.targetLanguage, - "domain": "general", - "query": text, - ]) - .validate() - .responseDecodable(of: AliResponse.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 - if let data = value.data, let translateText = data.translateText { - result.translatedResults = [translateText.unescapedXML()] - completion(result, nil) - } else { - let ezError = EZError(type: .API, description: value.code, errorDataMessage: value.message) - completion(result, ezError) - } - case let .failure(error): - NSLog("ali lookup error \(error)") - let ezError = EZError(nsError: error, errorResponseData: response.data) - completion(result, ezError) - } - } + method: hasToken ? .post : .get, + parameters: parameters) + .validate() + .responseDecodable(of: AliResponse.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 + if let data = value.data, let translateText = data.translateText { + result.translatedResults = [translateText.unescapedXML()] + completion(result, nil) + } else { + let ezError = EZError(type: .API, description: value.code, errorDataMessage: value.message) + completion(result, ezError) + } + canRetry = true + case let .failure(error): + // The result returned when the token expires is HTML. + if hasToken, error.isResponseSerializationError { + print("ali token invaild") + if canRetry { + tokenResponse = nil + canRetry = false + // Request token again. + translate(text, from: from, to: to, completion: completion) + } else { + self.request(token: nil, parameterName: nil, transType: transType, text: text, from: from, to: to, completion: completion) + } + + } else { + print("ali lookup error \(error)") + let ezError = EZError(nsError: error, errorResponseData: response.data) + completion(result, ezError) + } + } + } queryModel.setStop({ request.cancel() From 6316dc0563d5c8318952d969c4a590ade04585c2 Mon Sep 17 00:00:00 2001 From: choykarl <253440030@qq.com> Date: Fri, 22 Dec 2023 22:10:00 +0800 Subject: [PATCH 09/16] optimized code --- Easydict/Feature/Service/Ali/AliService.swift | 33 +++++++++++-------- 1 file changed, 20 insertions(+), 13 deletions(-) diff --git a/Easydict/Feature/Service/Ali/AliService.swift b/Easydict/Feature/Service/Ali/AliService.swift index 5c30dd36a..55b3e91a5 100644 --- a/Easydict/Feature/Service/Ali/AliService.swift +++ b/Easydict/Feature/Service/Ali/AliService.swift @@ -14,6 +14,14 @@ class AliService: QueryService { private(set) var tokenResponse: AliTokenResponse? private(set) var canRetry = true + var hasToken: (has: Bool, token: String, parameterName: String) { + if let token = tokenResponse?.token, let parameterName = tokenResponse?.parameterName, !token.isEmpty, !parameterName.isEmpty { + return (true, token, parameterName) + } else { + return (false, "", "") + } + } + override func serviceType() -> ServiceType { .ali } @@ -58,8 +66,8 @@ class AliService: QueryService { return } - if let token = tokenResponse?.token, let parameterName = tokenResponse?.parameterName, !token.isEmpty, !parameterName.isEmpty { - self.request(token: token, parameterName: parameterName, transType: transType, text: text, from: from, to: to, completion: completion) + if hasToken.has { + self.request(transType: transType, text: text, from: from, to: to, completion: completion) return } @@ -71,11 +79,11 @@ class AliService: QueryService { switch response.result { case let .success(value): tokenResponse = value - self.request(token: value.token, parameterName: value.parameterName, transType: transType, text: text, from: from, to: to, completion: completion) case let .failure(error): print("ali translate get token error: \(error)") - self.request(token: nil, parameterName: nil, transType: transType, text: text, from: from, to: to, completion: completion) } + + self.request(transType: transType, text: text, from: from, to: to, completion: completion) } queryModel.setStop({ @@ -84,8 +92,7 @@ class AliService: QueryService { } /// If there is a token, use the POST method and request with the token as a parameter; otherwise, use the GET method to request. - private func request(token: String?, parameterName: String?, transType: AliTranslateType, text: String, from: Language, to: Language, completion: @escaping (EZQueryResult, Error?) -> Void) { - var hasToken = false + private func request(transType: AliTranslateType, text: String, from: Language, to: Language, completion: @escaping (EZQueryResult, Error?) -> Void) { var parameters = [ "srcLang": transType.sourceLanguage, "tgtLang": transType.targetLanguage, @@ -93,13 +100,13 @@ class AliService: QueryService { "query": text, ] - if let token, let parameterName, !token.isEmpty, !parameterName.isEmpty { - hasToken = true - parameters[parameterName] = token + let hasToken = hasToken + if hasToken.has { + parameters[hasToken.parameterName] = hasToken.token } let request = AF.request("https://translate.alibaba.com/api/translate/text", - method: hasToken ? .post : .get, + method: hasToken.has ? .post : .get, parameters: parameters) .validate() .responseDecodable(of: AliResponse.self) { [weak self] response in @@ -121,15 +128,15 @@ class AliService: QueryService { canRetry = true case let .failure(error): // The result returned when the token expires is HTML. - if hasToken, error.isResponseSerializationError { + if hasToken.has, error.isResponseSerializationError { print("ali token invaild") + tokenResponse = nil if canRetry { - tokenResponse = nil canRetry = false // Request token again. translate(text, from: from, to: to, completion: completion) } else { - self.request(token: nil, parameterName: nil, transType: transType, text: text, from: from, to: to, completion: completion) + self.request(transType: transType, text: text, from: from, to: to, completion: completion) } } else { From 6128cae42806dddc5910ce1ba5ae4afb08f52f55 Mon Sep 17 00:00:00 2001 From: choykarl <253440030@qq.com> Date: Fri, 22 Dec 2023 22:40:22 +0800 Subject: [PATCH 10/16] ali translate text length limit --- Easydict/Feature/Service/Ali/AliService.swift | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/Easydict/Feature/Service/Ali/AliService.swift b/Easydict/Feature/Service/Ali/AliService.swift index 55b3e91a5..365a59d6e 100644 --- a/Easydict/Feature/Service/Ali/AliService.swift +++ b/Easydict/Feature/Service/Ali/AliService.swift @@ -57,6 +57,12 @@ class AliService: QueryService { } override func translate(_ text: String, from: Language, to: Language, completion: @escaping (EZQueryResult, Error?) -> Void) { + if text.count > 5000 { + let error = EZError(type: .none, description: "ali translate text length limit exceeded") + completion(result, error) + return + } + let transType = AliTranslateType.transType(from: from, to: to) guard transType != .unsupported else { let showingFrom = EZLanguageManager.shared().showingLanguageName(from) From 29e2763ac7c0086c73565a23c914cfba4d1cc7b2 Mon Sep 17 00:00:00 2001 From: choykarl <253440030@qq.com> Date: Fri, 22 Dec 2023 22:46:34 +0800 Subject: [PATCH 11/16] Detected by GitGuardian. --- Easydict/Feature/Service/Ali/AliResponse.swift | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Easydict/Feature/Service/Ali/AliResponse.swift b/Easydict/Feature/Service/Ali/AliResponse.swift index 6a8220b1d..ecf6392b4 100644 --- a/Easydict/Feature/Service/Ali/AliResponse.swift +++ b/Easydict/Feature/Service/Ali/AliResponse.swift @@ -48,9 +48,9 @@ struct AliResponse: Codable { /** { - "token": "edda7f08-d74d-43b1-b8ff-cc2fdf2b5507", - "parameterName": "_csrf", - "headerName": "X-XSRF-TOKEN_PROPERTY_ITEM" + "token": "", + "parameterName": "", + "headerName": "" } */ From 779f71ce9ad4ae93dedfdf3792ba37c59ff84bd7 Mon Sep 17 00:00:00 2001 From: choykarl <253440030@qq.com> Date: Fri, 22 Dec 2023 22:54:12 +0800 Subject: [PATCH 12/16] fix unsafely code --- Easydict/Feature/Service/Ali/AliService.swift | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/Easydict/Feature/Service/Ali/AliService.swift b/Easydict/Feature/Service/Ali/AliService.swift index 365a59d6e..caff1ef74 100644 --- a/Easydict/Feature/Service/Ali/AliService.swift +++ b/Easydict/Feature/Service/Ali/AliService.swift @@ -73,7 +73,7 @@ class AliService: QueryService { } if hasToken.has { - self.request(transType: transType, text: text, from: from, to: to, completion: completion) + request(transType: transType, text: text, from: from, to: to, completion: completion) return } @@ -84,7 +84,7 @@ class AliService: QueryService { guard let self else { return } switch response.result { case let .success(value): - tokenResponse = value + self.tokenResponse = value case let .failure(error): print("ali translate get token error: \(error)") } @@ -136,11 +136,11 @@ class AliService: QueryService { // The result returned when the token expires is HTML. if hasToken.has, error.isResponseSerializationError { print("ali token invaild") - tokenResponse = nil - if canRetry { - canRetry = false + self.tokenResponse = nil + if self.canRetry { + self.canRetry = false // Request token again. - translate(text, from: from, to: to, completion: completion) + self.translate(text, from: from, to: to, completion: completion) } else { self.request(transType: transType, text: text, from: from, to: to, completion: completion) } From 242a3a33305ab999066b79652c782d7423877be5 Mon Sep 17 00:00:00 2001 From: choykarl <253440030@qq.com> Date: Fri, 22 Dec 2023 23:01:34 +0800 Subject: [PATCH 13/16] fix unsafely code --- Easydict/Feature/Service/Ali/AliService.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Easydict/Feature/Service/Ali/AliService.swift b/Easydict/Feature/Service/Ali/AliService.swift index caff1ef74..543571f5f 100644 --- a/Easydict/Feature/Service/Ali/AliService.swift +++ b/Easydict/Feature/Service/Ali/AliService.swift @@ -131,7 +131,7 @@ class AliService: QueryService { let ezError = EZError(type: .API, description: value.code, errorDataMessage: value.message) completion(result, ezError) } - canRetry = true + self.canRetry = true case let .failure(error): // The result returned when the token expires is HTML. if hasToken.has, error.isResponseSerializationError { From 547b8a7efe70b44958b3d951e4d01cbb66321cae Mon Sep 17 00:00:00 2001 From: choykarl <253440030@qq.com> Date: Sat, 30 Dec 2023 13:56:22 +0800 Subject: [PATCH 14/16] add ali translate api support --- .../Feature/Service/Ali/AliResponse.swift | 35 +++- Easydict/Feature/Service/Ali/AliService.swift | 165 +++++++++++++++--- Easydict/Feature/Service/Model/EZConstKey.h | 3 + .../Utility/EZLinkParser/EZSchemeParser.m | 3 + 4 files changed, 181 insertions(+), 25 deletions(-) diff --git a/Easydict/Feature/Service/Ali/AliResponse.swift b/Easydict/Feature/Service/Ali/AliResponse.swift index ecf6392b4..59a6c16d7 100644 --- a/Easydict/Feature/Service/Ali/AliResponse.swift +++ b/Easydict/Feature/Service/Ali/AliResponse.swift @@ -32,7 +32,7 @@ import Foundation } */ -struct AliResponse: Codable { +struct AliWebResponse: Codable { struct Data: Codable { var translateText: String? var detectLanguage: String? @@ -46,6 +46,39 @@ struct AliResponse: Codable { var data: Data? } +/** + { + "Code" : "200", + "Data" : { + "Translated" : "你好", + "WordCount" : "5" + }, + "RequestId" : "xxxxx" + } + + { + "Code" : "InvalidAccessKeyId.NotFound", + "HostId" : "mt.aliyuncs.com", + "Message" : "Specified access key is not found.", + "Recommend" : "https:\/\/api.aliyun.com\/troubleshoot?q=InvalidAccessKeyId.NotFound&product=alimt&requestId=xxxxx", + "RequestId" : "xxxxx" + } + + */ +struct AliAPIResponse: Codable { + struct Data: Codable { + var Translated: String? + var WordCount: String? + } + + var Code: String? + var Data: Data? + var RequestId: String? + var Message: String? + var HostId: String? + var Recommend: String? +} + /** { "token": "", diff --git a/Easydict/Feature/Service/Ali/AliService.swift b/Easydict/Feature/Service/Ali/AliService.swift index 543571f5f..1b0c41542 100644 --- a/Easydict/Feature/Service/Ali/AliService.swift +++ b/Easydict/Feature/Service/Ali/AliService.swift @@ -7,14 +7,16 @@ // import Alamofire +import CryptoKit import Foundation @objc(EZAliService) class AliService: QueryService { private(set) var tokenResponse: AliTokenResponse? - private(set) var canRetry = true + private(set) var canWebRetry = true + private let dateFormatter = ISO8601DateFormatter() - var hasToken: (has: Bool, token: String, parameterName: String) { + private var hasToken: (has: Bool, token: String, parameterName: String) { if let token = tokenResponse?.token, let parameterName = tokenResponse?.parameterName, !token.isEmpty, !parameterName.isEmpty { return (true, token, parameterName) } else { @@ -57,11 +59,8 @@ class AliService: QueryService { } override func translate(_ text: String, from: Language, to: Language, completion: @escaping (EZQueryResult, Error?) -> Void) { - if text.count > 5000 { - let error = EZError(type: .none, description: "ali translate text length limit exceeded") - completion(result, error) - return - } + let limit = 5000 + let text = text.count > limit ? String(text[.. Void) { + func hmacSha1(key: String, params: String) -> String? { + guard + let secret = key.data(using: .utf8), + let what = params.data(using: .utf8) + else { + return nil + } + var hmac = HMAC(key: SymmetricKey(data: secret)) + hmac.update(data: what) + let mac = Data(hmac.finalize()) + return mac.base64EncodedString() + } + + + /// https://help.aliyun.com/zh/sdk/product-overview/rpc-mechanism?spm=a2c4g.11186623.0.i20#sectiondiv-6jf-89b-wfa + var param = [ + "FormatType": "text", + "SourceLanguage": transType.sourceLanguage, + "TargetLanguage": transType.targetLanguage, + "SourceText": text, + "Scene": "general", + + /// common + "Action": "TranslateGeneral", + "Version": "2018-10-12", + "Format": "JSON", + "AccessKeyId": id, + "SignatureNonce": UUID().uuidString, + "Timestamp": dateFormatter.string(from: Date()), + "SignatureMethod": "HMAC-SHA1", + "SignatureVersion": "1.0", + ] + + let allowedCharacterSet = CharacterSet(charactersIn: "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_.~") + + let sortParams = param.keys.sorted() + + var paramsEncodeErrorString = "" + let canonicalizedQueryString = sortParams.map { key in + guard let keyEncode = key.addingPercentEncoding(withAllowedCharacters: allowedCharacterSet), + let valueEncode = param[key]?.addingPercentEncoding(withAllowedCharacters: allowedCharacterSet) + else { + paramsEncodeErrorString = paramsEncodeErrorString + "\(key) param encoding error \n" + return "" + } + return "\(keyEncode)=\(valueEncode)" + }.joined(separator: "&") + + if !paramsEncodeErrorString.isEmpty { + completion(result, EZError(type: .API, description: paramsEncodeErrorString)) + return + } + + guard let slashEncode = "/".addingPercentEncoding(withAllowedCharacters: allowedCharacterSet), + let canonicalizedQueryStringEncode = canonicalizedQueryString.addingPercentEncoding(withAllowedCharacters: allowedCharacterSet) + else { + completion(result, EZError(type: .API, description: "encoding error")) + return + } + + let stringToSign = "POST" + "&" + slashEncode + "&" + canonicalizedQueryStringEncode + + guard let signData = stringToSign.data(using: .utf8), let utf8String = String(data: signData, encoding: .nonLossyASCII) else { + completion(result, EZError(type: .API, description: "signature error")) + return + } + + guard let signature = hmacSha1(key: secret + "&", params: utf8String) else { + completion(result, EZError(type: .API, description: "hmacSha1 error")) return } - // get request token - let request = AF.request("https://translate.alibaba.com/api/translate/csrftoken", method: .get) + param["Signature"] = signature + + let request = AF.request("https://mt.aliyuncs.com", method: .post, parameters: param) .validate() - .responseDecodable(of: AliTokenResponse.self) { [weak self] response in + .responseDecodable(of: AliAPIResponse.self) { [weak self] response in guard let self else { return } + let result = self.result + switch response.result { case let .success(value): - self.tokenResponse = value + result.from = from + result.to = to + result.queryText = text + if let data = value.Data, let translateText = data.Translated { + result.translatedResults = [translateText] + completion(result, nil) + print("ali api translate success") + } else { + let ezError = EZError(type: .API, description: value.Code, errorDataMessage: value.Message) + completion(result, ezError) + } case let .failure(error): - print("ali translate get token error: \(error)") + print("ali api translate error \(error)") + let ezError = EZError(nsError: error, errorResponseData: response.data) + completion(result, ezError) } - - self.request(transType: transType, text: text, from: from, to: to, completion: completion) } queryModel.setStop({ @@ -98,7 +214,7 @@ class AliService: QueryService { } /// If there is a token, use the POST method and request with the token as a parameter; otherwise, use the GET method to request. - private func request(transType: AliTranslateType, text: String, from: Language, to: Language, completion: @escaping (EZQueryResult, Error?) -> Void) { + private func requestByWeb(transType: AliTranslateType, text: String, from: Language, to: Language, completion: @escaping (EZQueryResult, Error?) -> Void) { var parameters = [ "srcLang": transType.sourceLanguage, "tgtLang": transType.targetLanguage, @@ -115,7 +231,7 @@ class AliService: QueryService { method: hasToken.has ? .post : .get, parameters: parameters) .validate() - .responseDecodable(of: AliResponse.self) { [weak self] response in + .responseDecodable(of: AliWebResponse.self) { [weak self] response in guard let self else { return } let result = self.result @@ -127,26 +243,27 @@ class AliService: QueryService { if let data = value.data, let translateText = data.translateText { result.translatedResults = [translateText.unescapedXML()] completion(result, nil) + print("ali web translate success") } else { let ezError = EZError(type: .API, description: value.code, errorDataMessage: value.message) completion(result, ezError) } - self.canRetry = true + self.canWebRetry = true case let .failure(error): // The result returned when the token expires is HTML. if hasToken.has, error.isResponseSerializationError { - print("ali token invaild") + print("ali web token invaild") self.tokenResponse = nil - if self.canRetry { - self.canRetry = false + if self.canWebRetry { + self.canWebRetry = false // Request token again. self.translate(text, from: from, to: to, completion: completion) } else { - self.request(transType: transType, text: text, from: from, to: to, completion: completion) + self.requestByWeb(transType: transType, text: text, from: from, to: to, completion: completion) } } else { - print("ali lookup error \(error)") + print("ali web translate error \(error)") let ezError = EZError(nsError: error, errorResponseData: response.data) completion(result, ezError) } diff --git a/Easydict/Feature/Service/Model/EZConstKey.h b/Easydict/Feature/Service/Model/EZConstKey.h index a52601712..be00eeea2 100644 --- a/Easydict/Feature/Service/Model/EZConstKey.h +++ b/Easydict/Feature/Service/Model/EZConstKey.h @@ -36,6 +36,9 @@ static NSString *const EZCaiyunToken = @"EZCaiyunToken"; static NSString *const EZTencentSecretId = @"EZTencentSecretId"; static NSString *const EZTencentSecretKey = @"EZTencentSecretKey"; +static NSString *const EZAliAccessKeyId = @"EZAliAccessKeyId"; +static NSString *const EZAliAccessKeySecret = @"EZAliAccessKeySecret"; + @interface EZConstKey : NSObject + (NSString *)constkey:(NSString *)key windowType:(EZWindowType)windowType; diff --git a/Easydict/Feature/Utility/EZLinkParser/EZSchemeParser.m b/Easydict/Feature/Utility/EZLinkParser/EZSchemeParser.m index 640c1c913..cd8ba1630 100644 --- a/Easydict/Feature/Utility/EZLinkParser/EZSchemeParser.m +++ b/Easydict/Feature/Utility/EZLinkParser/EZSchemeParser.m @@ -216,6 +216,9 @@ - (NSArray *)allowedReadWriteKeys { EZTencentSecretId, EZTencentSecretKey, EZBingCookieKey, + + EZAliAccessKeyId, + EZAliAccessKeySecret, EZIntelligentQueryModeKey, ]; From 50c01b9f66ffacf892686e8e9db06eca10df6f5c Mon Sep 17 00:00:00 2001 From: choykarl <253440030@qq.com> Date: Sat, 30 Dec 2023 19:25:20 +0800 Subject: [PATCH 15/16] improve response parsing --- .../Feature/Service/Ali/AliResponse.swift | 40 +++++++++++++++++-- Easydict/Feature/Service/Ali/AliService.swift | 22 +++++----- 2 files changed, 48 insertions(+), 14 deletions(-) diff --git a/Easydict/Feature/Service/Ali/AliResponse.swift b/Easydict/Feature/Service/Ali/AliResponse.swift index 59a6c16d7..1583fc153 100644 --- a/Easydict/Feature/Service/Ali/AliResponse.swift +++ b/Easydict/Feature/Service/Ali/AliResponse.swift @@ -39,9 +39,9 @@ struct AliWebResponse: Codable { } var requestId: String? - var success: Bool? + var success: Bool var httpStatusCode: Int? - var code: String? + var code: AnyCodable? var message: String? var data: Data? } @@ -71,7 +71,7 @@ struct AliAPIResponse: Codable { var WordCount: String? } - var Code: String? + var Code: AnyCodable? var Data: Data? var RequestId: String? var Message: String? @@ -92,3 +92,37 @@ struct AliTokenResponse: Codable { var parameterName: String? var headerName: String? } + +enum AnyCodable: Codable { + case string(String) + case int(Int) + + init(from decoder: Decoder) throws { + if let intValue = try? decoder.singleValueContainer().decode(Int.self) { + self = .int(intValue) + } else if let stringValue = try? decoder.singleValueContainer().decode(String.self) { + self = .string(stringValue) + } else { + throw try DecodingError.dataCorruptedError(in: decoder.singleValueContainer(), debugDescription: "Code is neither Int nor String") + } + } + + func encode(to encoder: Encoder) throws { + var container = encoder.singleValueContainer() + switch self { + case let .string(stringValue): + try container.encode(stringValue) + case let .int(intValue): + try container.encode(intValue) + } + } + + var stringValue: String? { + switch self { + case let .int(i): + return String(i) + case let .string(s): + return s + } + } +} diff --git a/Easydict/Feature/Service/Ali/AliService.swift b/Easydict/Feature/Service/Ali/AliService.swift index 1b0c41542..9751c29c1 100644 --- a/Easydict/Feature/Service/Ali/AliService.swift +++ b/Easydict/Feature/Service/Ali/AliService.swift @@ -73,8 +73,8 @@ class AliService: QueryService { /** use user's access key id and secret - easydict://writeKeyValue?EZAliAccessKeyId=xxx - easydict://writeKeyValue?EZAliAccessKeySecret=xxx + easydict://writeKeyValue?EZAliAccessKeyId=LTAI5tRxhkyLkhMhBo9wMZWt + easydict://writeKeyValue?EZAliAccessKeySecret=BSpVWkbmU1QbJVofJYFpPtKkDrTmaN */ if let id = UserDefaults.standard.string(forKey: EZAliAccessKeyId), let secret = UserDefaults.standard.string(forKey: EZAliAccessKeySecret), !id.isEmpty, !secret.isEmpty @@ -121,7 +121,6 @@ class AliService: QueryService { return mac.base64EncodedString() } - /// https://help.aliyun.com/zh/sdk/product-overview/rpc-mechanism?spm=a2c4g.11186623.0.i20#sectiondiv-6jf-89b-wfa var param = [ "FormatType": "text", @@ -198,12 +197,13 @@ class AliService: QueryService { completion(result, nil) print("ali api translate success") } else { - let ezError = EZError(type: .API, description: value.Code, errorDataMessage: value.Message) - completion(result, ezError) + completion(result, EZError(type: .API, description: value.Code?.stringValue, errorDataMessage: value.Message)) } case let .failure(error): - print("ali api translate error \(error)") - let ezError = EZError(nsError: error, errorResponseData: response.data) + print("ali api translate error: \(error.errorDescription ?? "")") + + let ezError = EZError(nsError: error, errorDataMessage: error.errorDescription) + completion(result, ezError) } } @@ -240,12 +240,12 @@ class AliService: QueryService { result.from = from result.to = to result.queryText = text - if let data = value.data, let translateText = data.translateText { + if value.success, let translateText = value.data?.translateText { result.translatedResults = [translateText.unescapedXML()] completion(result, nil) print("ali web translate success") } else { - let ezError = EZError(type: .API, description: value.code, errorDataMessage: value.message) + let ezError = EZError(type: .API, description: value.code?.stringValue, errorDataMessage: value.message) completion(result, ezError) } self.canWebRetry = true @@ -263,8 +263,8 @@ class AliService: QueryService { } } else { - print("ali web translate error \(error)") - let ezError = EZError(nsError: error, errorResponseData: response.data) + print("ali web translate error: \(error.errorDescription ?? "")") + let ezError = EZError(nsError: error, errorDataMessage: error.errorDescription) completion(result, ezError) } } From da70ef8bf8fe8889ac0eaf8c186bf3a13e155233 Mon Sep 17 00:00:00 2001 From: choykarl <253440030@qq.com> Date: Sat, 30 Dec 2023 19:58:23 +0800 Subject: [PATCH 16/16] display more detailed error information. --- Easydict/Feature/Service/Ali/AliService.swift | 26 ++++++++++++++----- 1 file changed, 19 insertions(+), 7 deletions(-) diff --git a/Easydict/Feature/Service/Ali/AliService.swift b/Easydict/Feature/Service/Ali/AliService.swift index 9751c29c1..6c74419f8 100644 --- a/Easydict/Feature/Service/Ali/AliService.swift +++ b/Easydict/Feature/Service/Ali/AliService.swift @@ -200,11 +200,16 @@ class AliService: QueryService { completion(result, EZError(type: .API, description: value.Code?.stringValue, errorDataMessage: value.Message)) } case let .failure(error): - print("ali api translate error: \(error.errorDescription ?? "")") - - let ezError = EZError(nsError: error, errorDataMessage: error.errorDescription) + var msg: String? + if let data = response.data { + let res = try? JSONDecoder().decode(AliAPIResponse.self, from: data) + msg = res?.Message + } else { + msg = error.errorDescription + } - completion(result, ezError) + print("ali api translate error: \(msg ?? "")") + completion(result, EZError(nsError: error, errorDataMessage: msg)) } } @@ -263,9 +268,16 @@ class AliService: QueryService { } } else { - print("ali web translate error: \(error.errorDescription ?? "")") - let ezError = EZError(nsError: error, errorDataMessage: error.errorDescription) - completion(result, ezError) + var msg: String? + if let data = response.data { + let res = try? JSONDecoder().decode(AliWebResponse.self, from: data) + msg = res?.message + } else { + msg = error.errorDescription + } + + print("ali web translate error: \(msg ?? "")") + completion(result, EZError(nsError: error, errorDataMessage: msg)) } } }