diff --git a/Easydict.xcodeproj/project.pbxproj b/Easydict.xcodeproj/project.pbxproj index b165df3c0..0ef126f70 100644 --- a/Easydict.xcodeproj/project.pbxproj +++ b/Easydict.xcodeproj/project.pbxproj @@ -207,6 +207,8 @@ 03F14A3B2956016B00CB7379 /* EZVolcanoTranslate.m in Sources */ = {isa = PBXBuildFile; fileRef = 03F14A3A2956016B00CB7379 /* EZVolcanoTranslate.m */; }; 03F25CB329327BC200E66A12 /* EZShortcut.m in Sources */ = {isa = PBXBuildFile; fileRef = 03F25CB229327BC200E66A12 /* EZShortcut.m */; }; 03F639952AA6CFBB009B9914 /* EZBingConfig.m in Sources */ = {isa = PBXBuildFile; fileRef = 03F639942AA6CFBB009B9914 /* EZBingConfig.m */; }; + 17BCAEF72B0DFF9000A7D372 /* EZNiuTransTranslateResponse.m in Sources */ = {isa = PBXBuildFile; fileRef = 17BCAEF52B0DFF9000A7D372 /* EZNiuTransTranslateResponse.m */; }; + 17BCAEF82B0DFF9000A7D372 /* EZNiuTransTranslate.m in Sources */ = {isa = PBXBuildFile; fileRef = 17BCAEF62B0DFF9000A7D372 /* EZNiuTransTranslate.m */; }; 27B7919E2AEC36A1006E07C6 /* Easydict.xcconfig in Resources */ = {isa = PBXBuildFile; fileRef = 27B7919C2AEC36A1006E07C6 /* Easydict.xcconfig */; }; 27B7919F2AEC36A1006E07C6 /* Easydict-debug.xcconfig in Resources */ = {isa = PBXBuildFile; fileRef = 27B7919D2AEC36A1006E07C6 /* Easydict-debug.xcconfig */; }; 6220AD5B2A82812300BBFB52 /* EZBingService.m in Sources */ = {isa = PBXBuildFile; fileRef = 6220AD5A2A82812300BBFB52 /* EZBingService.m */; }; @@ -629,6 +631,10 @@ 03F639932AA6CFBB009B9914 /* EZBingConfig.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = EZBingConfig.h; sourceTree = ""; }; 03F639942AA6CFBB009B9914 /* EZBingConfig.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = EZBingConfig.m; sourceTree = ""; }; 06E15747A7BD34D510ADC6A8 /* Pods-Easydict.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Easydict.debug.xcconfig"; path = "Target Support Files/Pods-Easydict/Pods-Easydict.debug.xcconfig"; sourceTree = ""; }; + 17BCAEF32B0DFF9000A7D372 /* EZNiuTransTranslateResponse.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = EZNiuTransTranslateResponse.h; sourceTree = ""; }; + 17BCAEF42B0DFF9000A7D372 /* EZNiuTransTranslate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = EZNiuTransTranslate.h; sourceTree = ""; }; + 17BCAEF52B0DFF9000A7D372 /* EZNiuTransTranslateResponse.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = EZNiuTransTranslateResponse.m; sourceTree = ""; }; + 17BCAEF62B0DFF9000A7D372 /* EZNiuTransTranslate.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = EZNiuTransTranslate.m; sourceTree = ""; }; 27B7919C2AEC36A1006E07C6 /* Easydict.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; path = Easydict.xcconfig; sourceTree = ""; }; 27B7919D2AEC36A1006E07C6 /* Easydict-debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; path = "Easydict-debug.xcconfig"; sourceTree = ""; }; 27B791A02AEC3A5C006E07C6 /* Easydict-debug.entitlements */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.entitlements; path = "Easydict-debug.entitlements"; sourceTree = ""; }; @@ -1144,6 +1150,7 @@ 03B0222B29231FA6001C7E63 /* Service */ = { isa = PBXGroup; children = ( + 17BCAEF22B0DFF9000A7D372 /* Niutrans */, 6220AD582A8280E800BBFB52 /* Bing */, 0399C6A929A8608000B4AFCC /* OpenAI */, 03F14A382956011400CB7379 /* Volcano */, @@ -1810,6 +1817,17 @@ path = Volcano; sourceTree = ""; }; + 17BCAEF22B0DFF9000A7D372 /* Niutrans */ = { + isa = PBXGroup; + children = ( + 17BCAEF42B0DFF9000A7D372 /* EZNiuTransTranslate.h */, + 17BCAEF62B0DFF9000A7D372 /* EZNiuTransTranslate.m */, + 17BCAEF32B0DFF9000A7D372 /* EZNiuTransTranslateResponse.h */, + 17BCAEF52B0DFF9000A7D372 /* EZNiuTransTranslateResponse.m */, + ); + path = Niutrans; + sourceTree = ""; + }; 6220AD582A8280E800BBFB52 /* Bing */ = { isa = PBXGroup; children = ( @@ -2209,6 +2227,7 @@ 03BDA7BC2A26DA280079D04F /* XPMArgumentSignature.m in Sources */, 03B0230229231FA6001C7E63 /* EZWordResultView.m in Sources */, 0399C6A529A747E600B4AFCC /* EZDeepLTranslateResponse.m in Sources */, + 17BCAEF82B0DFF9000A7D372 /* EZNiuTransTranslate.m in Sources */, 039F5506294B6E29004AB940 /* EZSettingViewController.m in Sources */, 03BD281E29481C0400F5891A /* EZAudioPlayer.m in Sources */, 03E02A2629250D1D00A10260 /* EZEventMonitor.m in Sources */, @@ -2291,6 +2310,7 @@ 03542A4F2937B64B00C34C33 /* EZYoudaoOCRResponse.m in Sources */, 03B0233929231FA6001C7E63 /* MMTool.m in Sources */, 03542A552937B7DE00C34C33 /* EZTranslateError.m in Sources */, + 17BCAEF72B0DFF9000A7D372 /* EZNiuTransTranslateResponse.m in Sources */, 03BDA7B82A26DA280079D04F /* XPMValuedArgument.m in Sources */, 036196762A000F5900806370 /* NSData+Base64.m in Sources */, 03BDA7BA2A26DA280079D04F /* XPMMutableAttributedArray.m in Sources */, diff --git a/Easydict/App/Assets.xcassets/service-icon/NiuTrans.imageset/Contents.json b/Easydict/App/Assets.xcassets/service-icon/NiuTrans.imageset/Contents.json new file mode 100644 index 000000000..305c5be87 --- /dev/null +++ b/Easydict/App/Assets.xcassets/service-icon/NiuTrans.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "小牛翻译.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git "a/Easydict/App/Assets.xcassets/service-icon/NiuTrans.imageset/\345\260\217\347\211\233\347\277\273\350\257\221.png" "b/Easydict/App/Assets.xcassets/service-icon/NiuTrans.imageset/\345\260\217\347\211\233\347\277\273\350\257\221.png" new file mode 100644 index 000000000..6d63592bd Binary files /dev/null and "b/Easydict/App/Assets.xcassets/service-icon/NiuTrans.imageset/\345\260\217\347\211\233\347\277\273\350\257\221.png" differ diff --git a/Easydict/App/Localizable.xcstrings b/Easydict/App/Localizable.xcstrings index 2263aea51..893731daf 100644 --- a/Easydict/App/Localizable.xcstrings +++ b/Easydict/App/Localizable.xcstrings @@ -2193,6 +2193,22 @@ } } }, + "niuTrans_translate" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "NiuTrans" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "小牛翻译" + } + } + } + }, "Youdao" : { "extractionState" : "manual", "localizations" : { diff --git a/Easydict/Feature/Service/Model/EZConstKey.h b/Easydict/Feature/Service/Model/EZConstKey.h index f222a128f..be5472b7b 100644 --- a/Easydict/Feature/Service/Model/EZConstKey.h +++ b/Easydict/Feature/Service/Model/EZConstKey.h @@ -30,6 +30,7 @@ static NSString *const EZOpenAIModelKey = @"EZOpenAIModelKey"; static NSString *const EZDeepLAuthKey = @"EZDeepLAuthKey"; static NSString *const EZBingCookieKey = @"EZBingCookieKey"; +static NSString *const EZNiuTransAPIKey = @"EZNiuTransAPIKey"; @interface EZConstKey : NSObject diff --git a/Easydict/Feature/Service/Model/EZEnumTypes.h b/Easydict/Feature/Service/Model/EZEnumTypes.h index 4579b3cd0..8c6dd62b0 100644 --- a/Easydict/Feature/Service/Model/EZEnumTypes.h +++ b/Easydict/Feature/Service/Model/EZEnumTypes.h @@ -38,7 +38,7 @@ FOUNDATION_EXPORT EZServiceType const EZServiceTypeVolcano; FOUNDATION_EXPORT EZServiceType const EZServiceTypeOpenAI; FOUNDATION_EXPORT EZServiceType const EZServiceTypeAppleDictionary; FOUNDATION_EXPORT EZServiceType const EZServiceTypeBing; - +FOUNDATION_EXPORT EZServiceType const EZServiceTypeNiuTrans; 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 bb64ee6ca..2a4ec85e1 100644 --- a/Easydict/Feature/Service/Model/EZEnumTypes.m +++ b/Easydict/Feature/Service/Model/EZEnumTypes.m @@ -18,7 +18,7 @@ NSString *const EZServiceTypeVolcano = @"Volcano"; NSString *const EZServiceTypeOpenAI = @"OpenAI"; NSString *const EZServiceTypeBing = @"Bing"; - +NSString *const EZServiceTypeNiuTrans = @"NiuTrans"; NSString *const EZServiceTypeAppleDictionary = @"AppleDictionary"; diff --git a/Easydict/Feature/Service/Model/EZServiceTypes.m b/Easydict/Feature/Service/Model/EZServiceTypes.m index 37d4fdc3e..e0f6d1515 100644 --- a/Easydict/Feature/Service/Model/EZServiceTypes.m +++ b/Easydict/Feature/Service/Model/EZServiceTypes.m @@ -17,6 +17,7 @@ #import "EZBingService.h" #import "EZConfiguration.h" #import "EZAppleDictionary.h" +#import "EZNiuTransTranslate.h" @interface EZServiceTypes () @@ -57,6 +58,7 @@ + (instancetype)allocWithZone:(struct _NSZone *)zone { EZServiceTypeBaidu, [EZBaiduTranslate class], EZServiceTypeBing, [EZBingService class], EZServiceTypeVolcano, [EZVolcanoTranslate class], + EZServiceTypeNiuTrans, [EZNiuTransTranslate class], nil]; return allServiceDict; } diff --git a/Easydict/Feature/Service/Niutrans/EZNiuTransTranslate.h b/Easydict/Feature/Service/Niutrans/EZNiuTransTranslate.h new file mode 100644 index 000000000..901a27ed7 --- /dev/null +++ b/Easydict/Feature/Service/Niutrans/EZNiuTransTranslate.h @@ -0,0 +1,17 @@ +// +// EZNiuTransTranslate.h +// Easydict +// +// Created by BigGuang97 on 2023/11/23. +// Copyright © 2022 izual. All rights reserved. +// + +#import "EZQueryService.h" + +NS_ASSUME_NONNULL_BEGIN + +@interface EZNiuTransTranslate : EZQueryService + +@end + +NS_ASSUME_NONNULL_END diff --git a/Easydict/Feature/Service/Niutrans/EZNiuTransTranslate.m b/Easydict/Feature/Service/Niutrans/EZNiuTransTranslate.m new file mode 100644 index 000000000..32fa9d6c8 --- /dev/null +++ b/Easydict/Feature/Service/Niutrans/EZNiuTransTranslate.m @@ -0,0 +1,190 @@ +// +// EZNiuTransTranslate.m +// Easydict +// +// Created by BigGuang97 on 2023/11/23. +// Copyright © 2023 izual. All rights reserved. +// + +#import "EZNiuTransTranslate.h" +#import "NSArray+EZChineseText.h" +#import "EZNiuTransTranslateResponse.h" +#import "FWEncryptorAES.h" + +static NSString *kNiuTransTranslateURL = @"https://api.niutrans.com/NiuTransServer/translation"; + + +@interface EZNiuTransTranslate () + +@property (nonatomic, copy) NSString *apiKey; + +@end + +@implementation EZNiuTransTranslate + +- (instancetype)init { + if (self = [super init]) { + } + return self; +} + +- (NSString *)apiKey { + // This is a test APIKey, please do not abuse it. It is recommended to go to the official website to apply for a personal APIKey. + NSString *defaultEncryptedAPIKey = @"O5C+RKrWBR5GLMtqiOHlyS6Ib9D8JPY7aN8/S49gwmRYZNcxpbQ6eeNso6KoJVeR"; + NSString *defaultAPIKey = [FWEncryptorAES decryptText:defaultEncryptedAPIKey key:[[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleName"]]; + NSString *apiKey = [[NSUserDefaults standardUserDefaults] stringForKey:EZNiuTransAPIKey]; + if (apiKey.length == 0) { + apiKey = defaultAPIKey; + } + return apiKey; +} + + +#pragma mark - 重写父类方法 + +- (EZServiceType)serviceType { + return EZServiceTypeNiuTrans; +} + +- (NSString *)name { + return NSLocalizedString(@"niuTrans_translate", nil); +} + +- (NSString *)link { + return kNiuTransTranslateURL; +} + +// Supported languages: https://niutrans.com/documents/contents/trans_text#languageList +- (MMOrderedDictionary *)supportLanguagesDictionary { + MMOrderedDictionary *orderedDict = [[MMOrderedDictionary alloc] initWithKeysAndObjects: + EZLanguageAuto, @"auto", + EZLanguageSimplifiedChinese, @"zh", + EZLanguageTraditionalChinese, @"cht", + EZLanguageEnglish, @"en", + EZLanguageJapanese, @"ja", + EZLanguageKorean, @"ko", + EZLanguageFrench, @"fr", + EZLanguageSpanish, @"es", + EZLanguagePortuguese, @"pt", + EZLanguageItalian, @"it", + EZLanguageGerman, @"de", + EZLanguageRussian, @"ru", + EZLanguageArabic, @"ar", + EZLanguageSwedish, @"sv", + EZLanguageRomanian, @"ro", + EZLanguageThai, @"th", + EZLanguageSlovak, @"sk", + EZLanguageDutch, @"nl", + EZLanguageHungarian, @"hu", + EZLanguageGreek, @"el", + EZLanguageDanish, @"da", + EZLanguageFinnish, @"fi", + EZLanguagePolish, @"pl", + EZLanguageCzech, @"cs", + EZLanguageTurkish, @"tr", + EZLanguageLithuanian, @"lt", + EZLanguageLatvian, @"lv", + EZLanguageUkrainian, @"uk", + EZLanguageBulgarian, @"bg", + EZLanguageIndonesian, @"id", + EZLanguageMalay, @"ms", + EZLanguageSlovenian, @"sl", + EZLanguageEstonian, @"et", + EZLanguageVietnamese, @"vi", + EZLanguagePersian, @"fa", + EZLanguageHindi, @"hi", + EZLanguageTelugu, @"te", + EZLanguageTamil, @"ta", + EZLanguageUrdu, @"ur", + EZLanguageFilipino, @"fil", + EZLanguageKhmer, @"km", + EZLanguageLao, @"lo", + EZLanguageBengali, @"bn", + EZLanguageBurmese, @"my", + EZLanguageNorwegian, @"no", + EZLanguageSerbian, @"sr", + EZLanguageCroatian, @"hr", + EZLanguageMongolian, @"mn", + EZLanguageHebrew, @"he", + nil]; + return orderedDict; +} + +- (void)translate:(NSString *)text from:(EZLanguage)from to:(EZLanguage)to completion:(void (^)(EZQueryResult *, NSError *_Nullable))completion { + if ([self prehandleQueryTextLanguage:text autoConvertChineseText:YES from:from to:to completion:completion]) { + return; + } + + [self niuTransTranslate:text from:from to:to completion:completion]; +} + +- (void)ocr:(EZQueryModel *)queryModel completion:(void (^)(EZOCRResult *_Nullable, NSError *_Nullable))completion { + NSLog(@"NiuTrans not support ocr"); +} + +#pragma mark - NiuTrans API + +- (void)niuTransTranslate:(NSString *)text from:(EZLanguage)from to:(EZLanguage)to completion:(void (^)(EZQueryResult *_Nullable, NSError *_Nullable))completion { + NSString *souceLangCode = [self languageCodeForLanguage:from]; + NSString *targetLangCode = [self languageCodeForLanguage:to]; + + // NiuTrans API free and NiuTrans pro API use different URL host + NSString *host = @"https://api.niutrans.com"; + NSString *url = [NSString stringWithFormat:@"%@/NiuTransServer/translation", host]; + + NSDictionary *params = @{ + @"apikey" : self.apiKey, + @"src_text" : text, + @"from" : souceLangCode, + @"to" : targetLangCode, + @"source" : @"Easydict" + }; + + AFHTTPSessionManager *manager = [AFHTTPSessionManager manager]; + manager.responseSerializer=[AFJSONResponseSerializer serializer]; + manager.responseSerializer.acceptableContentTypes = [NSSet setWithObjects:@"application/json", @"text/html", @"text/plain", nil]; + manager.session.configuration.timeoutIntervalForRequest = EZNetWorkTimeoutInterval; + NSURLSessionTask *task = [manager POST:url parameters:params progress:nil success:^(NSURLSessionDataTask *_Nonnull task, id _Nullable responseObject) { + EZNiuTransTranslateResponse *niuTransTranslateResult = [EZNiuTransTranslateResponse mj_objectWithKeyValues:responseObject]; + NSString *translatedText = niuTransTranslateResult.tgtText; + // When translated text has multiple paragraphs, it will have an extra line break at the end, which we need to remove. + translatedText = [translatedText stringByTrimmingCharactersInSet:[NSCharacterSet newlineCharacterSet]]; + + if (translatedText) { + self.result.translatedResults = [translatedText toParagraphs]; + self.result.raw = responseObject; + completion(self.result, nil); + } else { + NSString *errorCode = niuTransTranslateResult.errorCode; + NSString *errorMsg = niuTransTranslateResult.errorMsg; + if (errorCode.length) { + NSString *message = errorCode; + if (errorMsg) { + message = [NSString stringWithFormat:@"%@, %@", errorCode, errorMsg]; + } + NSError *error = [EZTranslateError errorWithType:EZErrorTypeAPI + message:message + request:task.currentRequest]; + completion(self.result, error); + } + } + } failure:^(NSURLSessionDataTask *_Nullable task, NSError *_Nonnull error) { + if ([self.queryModel isServiceStopped:self.serviceType]) { + return; + } + + if (error.code == NSURLErrorCancelled) { + return; + } + + NSLog(@"NiuTransTranslate error: %@", error); + + completion(self.result, error); + }]; + + [self.queryModel setStopBlock:^{ + [task cancel]; + } serviceType:self.serviceType]; +} + +@end diff --git a/Easydict/Feature/Service/Niutrans/EZNiuTransTranslateResponse.h b/Easydict/Feature/Service/Niutrans/EZNiuTransTranslateResponse.h new file mode 100644 index 000000000..c07f57e1d --- /dev/null +++ b/Easydict/Feature/Service/Niutrans/EZNiuTransTranslateResponse.h @@ -0,0 +1,53 @@ +// +// EZNiuTransTranslateResponse.h +// Easydict +// +// Created by BigGuang97 on 2023/11/23. +// Copyright © 2023 izual. All rights reserved. +// + +#import + +NS_ASSUME_NONNULL_BEGIN + + +@class EZNiuTransTranslateResponse; + +#pragma mark - Object interfaces + +/** + https://niutrans.com/documents/contents/trans_text#languageList + + sccuess: + { + from: "zh", + to: "en", + tgt_text: "Hello" + } + + failure + { + "apikey" : "", + "error_code" : "13002", + "error_msg" : "apikey is empty", + "from" : "en", + "src_text" : "good", + "to" : "zh" + } + */ + +@interface EZNiuTransTranslateResponse : NSObject + +@property (nonatomic, copy) NSString *from; +@property (nonatomic, copy) NSString *to; + +@property (nonatomic, copy, nullable) NSString *tgtText; + +@property (nonatomic, copy, nullable) NSString *srcText; +@property (nonatomic, copy, nullable) NSString *errorMsg; +@property (nonatomic, copy, nullable) NSString *errorCode; +@property (nonatomic, copy, nullable) NSString *apikey; + +@end + +NS_ASSUME_NONNULL_END diff --git a/Easydict/Feature/Service/Niutrans/EZNiuTransTranslateResponse.m b/Easydict/Feature/Service/Niutrans/EZNiuTransTranslateResponse.m new file mode 100644 index 000000000..d568b21a5 --- /dev/null +++ b/Easydict/Feature/Service/Niutrans/EZNiuTransTranslateResponse.m @@ -0,0 +1,22 @@ +// +// EZNiuTransTranslateResponse.m +// Easydict +// +// Created by BigGuang97 on 2023/11/23. +// Copyright © 2023 izual. All rights reserved. +// + +#import "EZNiuTransTranslateResponse.h" + +@implementation EZNiuTransTranslateResponse + ++ (NSDictionary *)mj_replacedKeyFromPropertyName { + return @{ + @"srcText" : @"src_text", + @"tgtText" : @"tgt_text", + @"errorMsg" : @"error_msg", + @"errorCode" : @"error_code", + }; +} + +@end diff --git a/Easydict/Feature/Utility/EZLinkParser/EZSchemeParser.m b/Easydict/Feature/Utility/EZLinkParser/EZSchemeParser.m index 35fe9e0eb..3983ef0e5 100644 --- a/Easydict/Feature/Utility/EZLinkParser/EZSchemeParser.m +++ b/Easydict/Feature/Utility/EZLinkParser/EZSchemeParser.m @@ -203,6 +203,7 @@ - (NSArray *)allowedReadWriteKeys { EZDeepLAuthKey, EZDeepLTranslationAPIKey, + EZNiuTransAPIKey, EZIntelligentQueryModeKey,