diff --git a/Easydict.xcodeproj/project.pbxproj b/Easydict.xcodeproj/project.pbxproj index fbca9c020..de5b32ec8 100644 --- a/Easydict.xcodeproj/project.pbxproj +++ b/Easydict.xcodeproj/project.pbxproj @@ -245,6 +245,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 */; }; + 62E2BF4A2B4082BA00E42D38 /* AliService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 62E2BF472B4082BA00E42D38 /* AliService.swift */; }; + 62E2BF4B2B4082BA00E42D38 /* AliResponse.swift in Sources */ = {isa = PBXBuildFile; fileRef = 62E2BF482B4082BA00E42D38 /* AliResponse.swift */; }; + 62E2BF4C2B4082BA00E42D38 /* AliTranslateType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 62E2BF492B4082BA00E42D38 /* AliTranslateType.swift */; }; 62ED29A22B15F1F500901F51 /* EZWrapView.m in Sources */ = {isa = PBXBuildFile; fileRef = 62ED29A12B15F1F500901F51 /* EZWrapView.m */; }; 9672D7D22B4008B40023B8FB /* MASShortcutBinder+EZMASShortcutBinder.m in Sources */ = {isa = PBXBuildFile; fileRef = 9672D7D12B4008B40023B8FB /* MASShortcutBinder+EZMASShortcutBinder.m */; }; A0B65CA0F31AC8ECFB8347CC /* Pods_EasydictTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 378E73A7EA8FC8FB9C975A63 /* Pods_EasydictTests.framework */; }; @@ -714,6 +717,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 = ""; }; + 62E2BF472B4082BA00E42D38 /* AliService.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AliService.swift; sourceTree = ""; }; + 62E2BF482B4082BA00E42D38 /* AliResponse.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AliResponse.swift; sourceTree = ""; }; + 62E2BF492B4082BA00E42D38 /* AliTranslateType.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AliTranslateType.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; }; @@ -1267,6 +1273,7 @@ 03B0222B29231FA6001C7E63 /* Service */ = { isa = PBXGroup; children = ( + 62E2BF462B4082BA00E42D38 /* Ali */, 17BCAEF22B0DFF9000A7D372 /* Niutrans */, 2746AEBF2AF95040005FE0A1 /* Caiyun */, C4DD01E72B12B3B00025EE8E /* Tencent */, @@ -2041,6 +2048,16 @@ path = Bing; sourceTree = ""; }; + 62E2BF462B4082BA00E42D38 /* Ali */ = { + isa = PBXGroup; + children = ( + 62E2BF472B4082BA00E42D38 /* AliService.swift */, + 62E2BF482B4082BA00E42D38 /* AliResponse.swift */, + 62E2BF492B4082BA00E42D38 /* AliTranslateType.swift */, + ); + path = Ali; + sourceTree = ""; + }; 62ED299F2B15F1BE00901F51 /* EZWrapView */ = { isa = PBXGroup; children = ( @@ -2486,6 +2503,7 @@ 0309E1F0292B4A5E00AFB76A /* NSView+EZGetViewController.m in Sources */, 03B0232F29231FA6001C7E63 /* MMCrashFileTool.m in Sources */, 03B0233629231FA6001C7E63 /* MMEventMonitor.m in Sources */, + 62E2BF4A2B4082BA00E42D38 /* AliService.swift in Sources */, 03B0233729231FA6001C7E63 /* MMMake.m in Sources */, 03B0232E29231FA6001C7E63 /* MMCrashSignalExceptionHandler.m in Sources */, 03BDA7C42A26DA280079D04F /* NSDictionary+RubyDescription.m in Sources */, @@ -2572,6 +2590,7 @@ 0333FDA32A035BEC00891515 /* NSArray+EZChineseText.m in Sources */, 03B0233229231FA6001C7E63 /* MMLog.swift in Sources */, 03DC7C5E2A3ABE28000BF7C9 /* EZConstKey.m in Sources */, + 62E2BF4C2B4082BA00E42D38 /* AliTranslateType.swift in Sources */, 03E3E7C22ADE318800812C84 /* EZQueryMenuTextView.m in Sources */, 03B0231829231FA6001C7E63 /* SnipWindowController.m in Sources */, 03542A342936F70F00C34C33 /* EZLanguageManager.m in Sources */, @@ -2633,6 +2652,7 @@ 03DC7C662A3CA465000BF7C9 /* HWSegmentedControl.m in Sources */, 037E006D2B3DC098006491C6 /* EZOpenAIService+EZPromptMessages.m in Sources */, 03B022E929231FA6001C7E63 /* AppDelegate.m in Sources */, + 62E2BF4B2B4082BA00E42D38 /* AliResponse.swift in Sources */, 03B0232729231FA6001C7E63 /* NSColor+MM.m in Sources */, 03B0233529231FA6001C7E63 /* MMFileLogFormatter.m in Sources */, 03DC38C1292CC97900922CB2 /* EZServiceInfo.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 000000000..78b628623 Binary files /dev/null and b/Easydict/App/Assets.xcassets/service-icon/Alibaba.imageset/ali translate.png differ diff --git a/Easydict/App/Easydict-Bridging-Header.h b/Easydict/App/Easydict-Bridging-Header.h index ae0574b88..2747a587f 100644 --- a/Easydict/App/Easydict-Bridging-Header.h +++ b/Easydict/App/Easydict-Bridging-Header.h @@ -21,3 +21,5 @@ #import "entry.h" #import "AppDelegate.h" #import "EZConfiguration.h" + +#import "NSString+EZConvenience.h" diff --git a/Easydict/App/Localizable.xcstrings b/Easydict/App/Localizable.xcstrings index cf9787fae..0ee5bae52 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..9baf0b2b5 --- /dev/null +++ b/Easydict/Feature/Service/Ali/AliResponse.swift @@ -0,0 +1,142 @@ +// +// AliResponse.swift +// Easydict +// +// Created by choykarl on 2023/12/20. +// Copyright © 2023 izual. All rights reserved. +// + +import Foundation + +/** + { + "requestId": "", + "success": true, + "httpStatusCode": 200, + "code": "", + "message": "", + "data": { + "translateText": "你好", + "detectLanguage": "en" + } + } + + error: + { + "requestId": "", + "success": false, + "httpStatusCode": 500, + "code": "ParamError", + "message": "Query length limit exceeded", + "data": null + } + */ + +struct AliWebResponse: Codable { + struct Data: Codable { + var translateText: String? + var detectLanguage: String? + } + + var requestId: String? + var success: Bool + var httpStatusCode: Int? + var code: AnyCodable? + var message: String? + var data: Data? +} + +/** + { + "Code" : "200", + "Data" : { + "Translated" : "你好", + "WordCount" : "5" + }, + "RequestId" : "" + } + + { + "Code" : "InvalidAccessKeyId.NotFound", + "HostId" : "mt.aliyuncs.com", + "Message" : "Specified access key is not found.", + "Recommend" : "", + "RequestId" : "" + } + + */ +struct AliAPIResponse: Codable { + struct Data: Codable { + var translated: String? + var wordCount: String? + + enum CodingKeys: String, CodingKey { + case translated = "Translated" + case wordCount = "WordCount" + } + } + + var code: AnyCodable? + var data: Data? + var requestId: String? + var message: String? + var hostId: String? + var recommend: String? + + enum CodingKeys: String, CodingKey { + case data = "Data" + case code = "Code" + case requestId = "RequestId" + case message = "Message" + case hostId = "HostId" + case recommend = "Recommend" + } +} + +/** + { + "token": "", + "parameterName": "", + "headerName": "" + } + */ + +struct AliTokenResponse: Codable { + var token: String? + 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 new file mode 100644 index 000000000..6ba555109 --- /dev/null +++ b/Easydict/Feature/Service/Ali/AliService.swift @@ -0,0 +1,289 @@ +// +// AliService.swift +// Easydict +// +// Created by choykarl on 2023/12/20. +// Copyright © 2023 izual. All rights reserved. +// + +import Alamofire +import CryptoKit +import Foundation + +@objc(EZAliService) +class AliService: QueryService { + private(set) var tokenResponse: AliTokenResponse? + private(set) var canWebRetry = true + private let dateFormatter = ISO8601DateFormatter() + + 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 { + return (false, "", "") + } + } + + 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 { + print("ali Translate does not support OCR") + 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 limit = 5000 + let text = String(text.prefix(limit)) + + 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 + } + + /** + use user's access key id and secret + easydict://writeKeyValue?EZAliAccessKeyId= + easydict://writeKeyValue?EZAliAccessKeySecret= + */ + if let id = UserDefaults.standard.string(forKey: EZAliAccessKeyId), + let secret = UserDefaults.standard.string(forKey: EZAliAccessKeySecret), !id.isEmpty, !secret.isEmpty + { + requestByAPI(id: id, secret: secret, transType: transType, text: text, from: from, to: to, completion: completion) + } else { // use web api + if hasToken.has { + requestByWeb(transType: transType, text: text, from: from, to: to, completion: completion) + return + } + + // get web 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): + self.tokenResponse = value + case let .failure(error): + print("ali translate get token error: \(error)") + } + + self.requestByWeb(transType: transType, text: text, from: from, to: to, completion: completion) + } + + queryModel.setStop({ + request.cancel() + }, serviceType: serviceType().rawValue) + } + } + + private func requestByAPI(id: String, secret: String, transType: AliTranslateType, text: String, from: Language, to: Language, completion: @escaping (EZQueryResult, Error?) -> 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 + } + + param["Signature"] = signature + + let request = AF.request("https://mt.aliyuncs.com", method: .post, parameters: param) + .validate() + .responseDecodable(of: AliAPIResponse.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.translated { + result.translatedResults = [translateText] + completion(result, nil) + print("ali api translate success") + } else { + completion(result, EZError(type: .API, description: value.code?.stringValue, errorDataMessage: value.message)) + } + case let .failure(error): + var msg: String? + if let data = response.data { + let res = try? JSONDecoder().decode(AliAPIResponse.self, from: data) + msg = res?.message + } else { + msg = error.errorDescription + } + + print("ali api translate error: \(msg ?? "")") + completion(result, EZError(nsError: error, errorDataMessage: msg)) + } + } + + 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 requestByWeb(transType: AliTranslateType, text: String, from: Language, to: Language, completion: @escaping (EZQueryResult, Error?) -> Void) { + var parameters = [ + "srcLang": transType.sourceLanguage, + "tgtLang": transType.targetLanguage, + "domain": "general", + "query": text, + ] + + let hasToken = hasToken + if hasToken.has { + parameters[hasToken.parameterName] = hasToken.token + } + + let request = AF.request("https://translate.alibaba.com/api/translate/text", + method: hasToken.has ? .post : .get, + parameters: parameters) + .validate() + .responseDecodable(of: AliWebResponse.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 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?.stringValue, errorDataMessage: value.message) + completion(result, ezError) + } + self.canWebRetry = true + case let .failure(error): + // The result returned when the token expires is HTML. + if hasToken.has, error.isResponseSerializationError { + print("ali web token invaild") + self.tokenResponse = nil + if self.canWebRetry { + self.canWebRetry = false + // Request token again. + self.translate(text, from: from, to: to, completion: completion) + } else { + self.requestByWeb(transType: transType, text: text, from: from, to: to, completion: completion) + } + + } else { + 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)) + } + } + } + + 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..4960794e9 --- /dev/null +++ b/Easydict/Feature/Service/Ali/AliTranslateType.swift @@ -0,0 +1,103 @@ +// +// 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/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: "zh", // "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 { + /** + 文本翻译除繁体中文、蒙语、粤语外,其他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 + } + 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/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/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..d63ed6649 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/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 } diff --git a/Easydict/Feature/Utility/EZCategory/NSString/NSString+EZConvenience.h b/Easydict/Feature/Utility/EZCategory/NSString/NSString+EZConvenience.h index 46e19688e..6e9d879e3 100644 --- a/Easydict/Feature/Utility/EZCategory/NSString/NSString+EZConvenience.h +++ b/Easydict/Feature/Utility/EZCategory/NSString/NSString+EZConvenience.h @@ -40,6 +40,7 @@ NS_ASSUME_NONNULL_BEGIN /// Replace \" with " - (NSString *)escapedXMLString; +- (NSString *)unescapedXMLString; - (void)copyToPasteboard; - (void)copyToPasteboardSafely; 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, ];