diff --git a/.idea/.gitignore b/.idea/.gitignore new file mode 100644 index 000000000..35410cacd --- /dev/null +++ b/.idea/.gitignore @@ -0,0 +1,8 @@ +# 默认忽略的文件 +/shelf/ +/workspace.xml +# 基于编辑器的 HTTP 客户端请求 +/httpRequests/ +# Datasource local storage ignored files +/dataSources/ +/dataSources.local.xml diff --git a/.idea/Easydict.iml b/.idea/Easydict.iml new file mode 100644 index 000000000..74121dcb3 --- /dev/null +++ b/.idea/Easydict.iml @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/.idea/dictionaries/choikarl.xml b/.idea/dictionaries/choikarl.xml new file mode 100644 index 000000000..6ff843d04 --- /dev/null +++ b/.idea/dictionaries/choikarl.xml @@ -0,0 +1,11 @@ + + + + cyrl + easydict + hant + izual + mong + + + \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml new file mode 100644 index 000000000..1ed671e84 --- /dev/null +++ b/.idea/misc.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml new file mode 100644 index 000000000..11fe4ce1e --- /dev/null +++ b/.idea/modules.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/.idea/runConfigurations/Easydict.xml b/.idea/runConfigurations/Easydict.xml new file mode 100644 index 000000000..d5007ad2d --- /dev/null +++ b/.idea/runConfigurations/Easydict.xml @@ -0,0 +1,11 @@ + + + + + + + + + + \ No newline at end of file diff --git a/.idea/runConfigurations/EasydictHelper.xml b/.idea/runConfigurations/EasydictHelper.xml new file mode 100644 index 000000000..d39ceb0f1 --- /dev/null +++ b/.idea/runConfigurations/EasydictHelper.xml @@ -0,0 +1,11 @@ + + + + + + + + + + \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml new file mode 100644 index 000000000..35eb1ddfb --- /dev/null +++ b/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/.idea/xcode.xml b/.idea/xcode.xml new file mode 100644 index 000000000..a53a2b3ea --- /dev/null +++ b/.idea/xcode.xml @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/Easydict.xcodeproj/project.pbxproj b/Easydict.xcodeproj/project.pbxproj index 94843af26..da04be95e 100644 --- a/Easydict.xcodeproj/project.pbxproj +++ b/Easydict.xcodeproj/project.pbxproj @@ -197,6 +197,10 @@ 03F0DB382953428300EBF9C1 /* EZLog.m in Sources */ = {isa = PBXBuildFile; fileRef = 03F0DB372953428300EBF9C1 /* EZLog.m */; }; 03F14A3B2956016B00CB7379 /* EZVolcanoTranslate.m in Sources */ = {isa = PBXBuildFile; fileRef = 03F14A3A2956016B00CB7379 /* EZVolcanoTranslate.m */; }; 03F25CB329327BC200E66A12 /* EZShortcut.m in Sources */ = {isa = PBXBuildFile; fileRef = 03F25CB229327BC200E66A12 /* EZShortcut.m */; }; + 6220AD5B2A82812300BBFB52 /* EZMicrosoftService.m in Sources */ = {isa = PBXBuildFile; fileRef = 6220AD5A2A82812300BBFB52 /* EZMicrosoftService.m */; }; + 6295DE312A84D82E006145F4 /* EZMicrosoftTranslateModel.m in Sources */ = {isa = PBXBuildFile; fileRef = 6295DE302A84D82E006145F4 /* EZMicrosoftTranslateModel.m */; }; + 6295DE342A84EF76006145F4 /* EZMicrosoftLookupModel.m in Sources */ = {isa = PBXBuildFile; fileRef = 6295DE332A84EF76006145F4 /* EZMicrosoftLookupModel.m */; }; + 62A2D03F2A82967F007EEB01 /* EZMicrosoftRequest.m in Sources */ = {isa = PBXBuildFile; fileRef = 62A2D03E2A82967F007EEB01 /* EZMicrosoftRequest.m */; }; B87AC7E36367075BA5D13234 /* Pods_Easydict.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 6372B33DFF803C7096A82250 /* Pods_Easydict.framework */; }; C98CAE75239F4619005F7DCA /* EasydictHelper.app in CopyFiles */ = {isa = PBXBuildFile; fileRef = C90BE309239F38EB00ADE88B /* EasydictHelper.app */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; }; /* End PBXBuildFile section */ @@ -585,6 +589,14 @@ 03F25CB129327BC200E66A12 /* EZShortcut.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = EZShortcut.h; sourceTree = ""; }; 03F25CB229327BC200E66A12 /* EZShortcut.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = EZShortcut.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 = ""; }; + 6220AD592A82812300BBFB52 /* EZMicrosoftService.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = EZMicrosoftService.h; sourceTree = ""; }; + 6220AD5A2A82812300BBFB52 /* EZMicrosoftService.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = EZMicrosoftService.m; sourceTree = ""; }; + 6295DE2F2A84D82E006145F4 /* EZMicrosoftTranslateModel.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = EZMicrosoftTranslateModel.h; sourceTree = ""; }; + 6295DE302A84D82E006145F4 /* EZMicrosoftTranslateModel.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = EZMicrosoftTranslateModel.m; sourceTree = ""; }; + 6295DE322A84EF76006145F4 /* EZMicrosoftLookupModel.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = EZMicrosoftLookupModel.h; sourceTree = ""; }; + 6295DE332A84EF76006145F4 /* EZMicrosoftLookupModel.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = EZMicrosoftLookupModel.m; sourceTree = ""; }; + 62A2D03D2A82967F007EEB01 /* EZMicrosoftRequest.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = EZMicrosoftRequest.h; sourceTree = ""; }; + 62A2D03E2A82967F007EEB01 /* EZMicrosoftRequest.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = EZMicrosoftRequest.m; sourceTree = ""; }; 6372B33DFF803C7096A82250 /* Pods_Easydict.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Easydict.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 91E3E579C6DB88658B4BB102 /* Pods-Easydict.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Easydict.release.xcconfig"; path = "Target Support Files/Pods-Easydict/Pods-Easydict.release.xcconfig"; sourceTree = ""; }; C90BE309239F38EB00ADE88B /* EasydictHelper.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = EasydictHelper.app; sourceTree = BUILT_PRODUCTS_DIR; }; @@ -1048,6 +1060,7 @@ 03B0222B29231FA6001C7E63 /* Service */ = { isa = PBXGroup; children = ( + 6220AD582A8280E800BBFB52 /* Microsoft */, 0399C6A929A8608000B4AFCC /* OpenAI */, 03F14A382956011400CB7379 /* Volcano */, 03BD281B29481BE100F5891A /* AudioPlayer */, @@ -1694,6 +1707,21 @@ path = Volcano; sourceTree = ""; }; + 6220AD582A8280E800BBFB52 /* Microsoft */ = { + isa = PBXGroup; + children = ( + 6220AD592A82812300BBFB52 /* EZMicrosoftService.h */, + 6220AD5A2A82812300BBFB52 /* EZMicrosoftService.m */, + 62A2D03D2A82967F007EEB01 /* EZMicrosoftRequest.h */, + 62A2D03E2A82967F007EEB01 /* EZMicrosoftRequest.m */, + 6295DE2F2A84D82E006145F4 /* EZMicrosoftTranslateModel.h */, + 6295DE302A84D82E006145F4 /* EZMicrosoftTranslateModel.m */, + 6295DE322A84EF76006145F4 /* EZMicrosoftLookupModel.h */, + 6295DE332A84EF76006145F4 /* EZMicrosoftLookupModel.m */, + ); + path = Microsoft; + sourceTree = ""; + }; 713A345D86B5BC86D158B68F /* Frameworks */ = { isa = PBXGroup; children = ( @@ -1934,6 +1962,7 @@ 035E37E72A0953120061DFAF /* EZToast.m in Sources */, 03542A492937B5CF00C34C33 /* EZGoogleTranslate.m in Sources */, 03D0435A2928C4C800E7559E /* EZWindowManager.m in Sources */, + 6295DE342A84EF76006145F4 /* EZMicrosoftLookupModel.m in Sources */, 03B0230729231FA6001C7E63 /* EZCommonView.m in Sources */, 03B0233329231FA6001C7E63 /* MMLog.m in Sources */, 0309E1F4292BD6A100AFB76A /* EZQueryModel.m in Sources */, @@ -1998,6 +2027,7 @@ 03DC7C5E2A3ABE28000BF7C9 /* EZConstKey.m in Sources */, 03B0231829231FA6001C7E63 /* SnipWindowController.m in Sources */, 03542A342936F70F00C34C33 /* EZLanguageManager.m in Sources */, + 6295DE312A84D82E006145F4 /* EZMicrosoftTranslateModel.m in Sources */, 0361967B2A0037F700806370 /* NSData+EZMD5.m in Sources */, 03BFFC68295F4B87004E033E /* EZYoudaoDictModel.m in Sources */, 03247E3A296AE8EC00AFCD67 /* EZLoadingAnimationView.m in Sources */, @@ -2009,6 +2039,7 @@ 039CC90D292F664E0037B91E /* NSObject+EZWindowType.m in Sources */, 03B0232229231FA6001C7E63 /* NSImage+MM.m in Sources */, 03BB2DEF29F59C8A00447EDD /* EZSymbolImageButton.m in Sources */, + 62A2D03F2A82967F007EEB01 /* EZMicrosoftRequest.m in Sources */, 03BDA7BE2A26DA280079D04F /* XPMCountedArgument.m in Sources */, 03B022FE29231FA6001C7E63 /* EZBaseQueryViewController.m in Sources */, 0396D611292C932F006A11D9 /* EZSelectLanguageCell.m in Sources */, @@ -2060,6 +2091,7 @@ 03B0231429231FA6001C7E63 /* DarkModeManager.m in Sources */, 03BDA7C02A26DA280079D04F /* XPMArgumentPackage.m in Sources */, 037852B02957FEB200D0E2CF /* EZServiceViewController.m in Sources */, + 6220AD5B2A82812300BBFB52 /* EZMicrosoftService.m in Sources */, 039F5508294B6E29004AB940 /* EZAboutViewController.m in Sources */, 03D8A6592A42A1A300D9A968 /* EZAppModel.m in Sources */, 036E7D7B293F4FC8002675DF /* EZOpenLinkButton.m in Sources */, @@ -2133,7 +2165,7 @@ COMBINE_HIDPI_IMAGES = YES; CURRENT_PROJECT_VERSION = 19; DEAD_CODE_STRIPPING = YES; - DEVELOPMENT_TEAM = 79NQA2XYHM; + DEVELOPMENT_TEAM = Q37CNASBM2; INFOPLIST_FILE = EasydictHelper/Info.plist; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", @@ -2158,7 +2190,7 @@ COMBINE_HIDPI_IMAGES = YES; CURRENT_PROJECT_VERSION = 19; DEAD_CODE_STRIPPING = YES; - DEVELOPMENT_TEAM = 79NQA2XYHM; + DEVELOPMENT_TEAM = Q37CNASBM2; INFOPLIST_FILE = EasydictHelper/Info.plist; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", @@ -2304,7 +2336,7 @@ COPY_PHASE_STRIP = NO; CURRENT_PROJECT_VERSION = 19; DEAD_CODE_STRIPPING = YES; - DEVELOPMENT_TEAM = 79NQA2XYHM; + DEVELOPMENT_TEAM = Q37CNASBM2; FRAMEWORK_SEARCH_PATHS = "$(inherited)"; GCC_PREFIX_HEADER = "$(SRCROOT)/Easydict/App/PrefixHeader.pch"; INFOPLIST_FILE = "$(TARGET_NAME)/App/Info.plist"; @@ -2340,7 +2372,7 @@ COPY_PHASE_STRIP = NO; CURRENT_PROJECT_VERSION = 19; DEAD_CODE_STRIPPING = YES; - DEVELOPMENT_TEAM = 79NQA2XYHM; + DEVELOPMENT_TEAM = Q37CNASBM2; FRAMEWORK_SEARCH_PATHS = "$(inherited)"; GCC_PREFIX_HEADER = "$(SRCROOT)/Easydict/App/PrefixHeader.pch"; INFOPLIST_FILE = "$(TARGET_NAME)/App/Info.plist"; diff --git a/Easydict.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/Easydict.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 000000000..18d981003 --- /dev/null +++ b/Easydict.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/Easydict/App/Assets.xcassets/service-icon/Microsoft.imageset/Contents.json b/Easydict/App/Assets.xcassets/service-icon/Microsoft.imageset/Contents.json new file mode 100644 index 000000000..5abc8bf9e --- /dev/null +++ b/Easydict/App/Assets.xcassets/service-icon/Microsoft.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "Microsoft Translate.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Easydict/App/Assets.xcassets/service-icon/Microsoft.imageset/Microsoft Translate.png b/Easydict/App/Assets.xcassets/service-icon/Microsoft.imageset/Microsoft Translate.png new file mode 100644 index 000000000..fb196bf1f Binary files /dev/null and b/Easydict/App/Assets.xcassets/service-icon/Microsoft.imageset/Microsoft Translate.png differ diff --git a/Easydict/Feature/Service/Microsoft/EZMicrosoftLookupModel.h b/Easydict/Feature/Service/Microsoft/EZMicrosoftLookupModel.h new file mode 100644 index 000000000..f9b86f5ac --- /dev/null +++ b/Easydict/Feature/Service/Microsoft/EZMicrosoftLookupModel.h @@ -0,0 +1,35 @@ +// +// EZMicrosoftLookupModel.h +// Easydict +// +// Created by ChoiKarl on 2023/8/10. +// Copyright © 2023 izual. All rights reserved. +// + +#import + +NS_ASSUME_NONNULL_BEGIN + +@interface EZMicrosoftLookupBackTranslationsModel : NSObject +@property (nonatomic, copy) NSString *normalizedText; +@property (nonatomic, copy) NSString *displayText; +@property (nonatomic, assign) NSInteger numExamples; +@property (nonatomic, assign) NSInteger frequencyCount; +@end + +@interface EZMicrosoftLookupTranslationsModel : NSObject +@property (nonatomic, copy) NSString *normalizedTarget; +@property (nonatomic, copy) NSString *displayTarget; +@property (nonatomic, copy) NSString *posTag; +@property (nonatomic, assign) double confidence; +@property (nonatomic, copy) NSString *prefixWord; +@property (nonatomic, strong) NSArray *backTranslations; +@end + +@interface EZMicrosoftLookupModel : NSObject +@property (nonatomic, copy) NSString *normalizedSource; +@property (nonatomic, copy) NSString *displaySource; +@property (nonatomic, strong) NSArray *translations; +@end + +NS_ASSUME_NONNULL_END diff --git a/Easydict/Feature/Service/Microsoft/EZMicrosoftLookupModel.m b/Easydict/Feature/Service/Microsoft/EZMicrosoftLookupModel.m new file mode 100644 index 000000000..095403ca0 --- /dev/null +++ b/Easydict/Feature/Service/Microsoft/EZMicrosoftLookupModel.m @@ -0,0 +1,31 @@ +// +// EZMicrosoftLookupModel.m +// Easydict +// +// Created by ChoiKarl on 2023/8/10. +// Copyright © 2023 izual. All rights reserved. +// + +#import "EZMicrosoftLookupModel.h" + +@implementation EZMicrosoftLookupBackTranslationsModel + +@end + +@implementation EZMicrosoftLookupTranslationsModel ++ (NSDictionary *)mj_objectClassInArray { + return @{ + @"backTranslations": [EZMicrosoftLookupBackTranslationsModel class] + }; +} + +@end + +@implementation EZMicrosoftLookupModel ++ (NSDictionary *)mj_objectClassInArray { + return @{ + @"translations": [EZMicrosoftLookupTranslationsModel class] + }; +} + +@end diff --git a/Easydict/Feature/Service/Microsoft/EZMicrosoftRequest.h b/Easydict/Feature/Service/Microsoft/EZMicrosoftRequest.h new file mode 100644 index 000000000..e212553a7 --- /dev/null +++ b/Easydict/Feature/Service/Microsoft/EZMicrosoftRequest.h @@ -0,0 +1,24 @@ +// +// EZMicrosoftRequest.h +// Easydict +// +// Created by ChoiKarl on 2023/8/8. +// Copyright © 2023 izual. All rights reserved. +// + +#import + +NS_ASSUME_NONNULL_BEGIN + +static NSString * const kTranslatorHost = @"https://www.bing.com/translator"; + +typedef void(^MicrosoftTranslateCompletion)(NSData * _Nullable translateData, NSData * _Nullable lookupData, NSError * _Nullable translateError, NSError * _Nullable lookupError); + +@interface EZMicrosoftRequest : NSObject + +- (void)translateWithFrom:(NSString *)from to:(NSString *)to text:(NSString *)text completionHandler:(MicrosoftTranslateCompletion)completion; + +- (void)reset; +@end + +NS_ASSUME_NONNULL_END diff --git a/Easydict/Feature/Service/Microsoft/EZMicrosoftRequest.m b/Easydict/Feature/Service/Microsoft/EZMicrosoftRequest.m new file mode 100644 index 000000000..756cadc93 --- /dev/null +++ b/Easydict/Feature/Service/Microsoft/EZMicrosoftRequest.m @@ -0,0 +1,273 @@ +// +// EZMicrosoftRequest.m +// Easydict +// +// Created by ChoiKarl on 2023/8/8. +// Copyright © 2023 izual. All rights reserved. +// + +NSString * const kTTranslateV3Host = @"https://www.bing.com/ttranslatev3"; +NSString * const kTLookupV3Host = @"https://www.bing.com/tlookupv3"; + +// memory cache +static NSString *kIG; +static NSString *kIID; +static NSString *kToken; +static NSString *kKey; + +#import "EZMicrosoftRequest.h" +#import "EZTranslateError.h" + +@interface EZMicrosoftRequest () +@property (nonatomic, strong) AFHTTPSessionManager *htmlSession; +@property (nonatomic, strong) AFHTTPSessionManager *translateSession; +@property (nonatomic, strong) NSData *translateData; +@property (nonatomic, strong) NSData *lookupData; +@property (nonatomic, strong) NSError *translateError; +@property (nonatomic, strong) NSError *lookupError; +@property (nonatomic, assign) NSInteger responseCount; +@property (nonatomic, copy) MicrosoftTranslateCompletion completion; +@end + +@implementation EZMicrosoftRequest + +- (void)executeCallback { + self.responseCount += 1; + if (self.responseCount >= 2) { + if (self.completion != nil) { + self.completion([self.translateData copy], [self.lookupData copy], [self.translateError copy], [self.lookupError copy]); + } + [self resetData]; + } +} + +- (void)fetchTranslateParam:(void (^)(NSString * IG, NSString * IID, NSString * token, NSString * key))paramCallback failure:(nonnull void (^)(NSError * _Nonnull))failure { + if (kIG.length > 0 && kIID.length > 0 && kToken.length > 0 && kKey.length > 0) { + paramCallback(kIG, kIID, kToken, kKey); + return; + } + + [self.htmlSession GET:kTranslatorHost parameters:nil progress:nil success:^(NSURLSessionDataTask * _Nonnull task, id _Nullable responseObject) { + if (![responseObject isKindOfClass:[NSData class]]) { + failure(EZTranslateError(EZErrorTypeAPI, @"microsoft htmlSession responseObject is not NSData", nil)); + NSLog(@"microsoft html responseObject type is %@", [responseObject class]); + return; + } + NSString *responseString = [[NSString alloc] initWithData:responseObject encoding:NSUTF8StringEncoding]; + + NSString *IG = [self getIGValueFromHTML:responseString]; + if (IG.length == 0) { + failure(EZTranslateError(EZErrorTypeAPI, @"microsoft IG is empty", nil)); + return; + } + kIG = IG; + NSLog(@"microsoft IG: %@", IG); + + NSString *IID = [self getValueOfDataIidFromHTML:responseString]; + if (IID.length == 0) { + failure(EZTranslateError(EZErrorTypeAPI, @"microsoft IID is empty", nil)); + return; + } + kIID = IID; + NSLog(@"microsoft IID: %@", IID); + + NSArray *arr = [self getParamsAbusePreventionHelperArrayFromHTML:responseString]; + if (arr.count != 3) { + failure(EZTranslateError(EZErrorTypeAPI, @"microsoft get key and token failed", nil)); + return; + } + NSString *key = arr[0]; + if (key.length == 0) { + failure(EZTranslateError(EZErrorTypeAPI, @"microsoft key is empey", nil)); + return; + } + NSString *token = arr[1]; + if (token.length == 0) { + failure(EZTranslateError(EZErrorTypeAPI, @"microsoft token is empey", nil)); + return; + } + kKey = key; + NSLog(@"microsoft key: %@", key); + kToken = token; + NSLog(@"microsoft token: %@", token); + paramCallback(IG, IID, token, key); + } failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) { + failure(error); + }]; +} + +- (void)translateWithFrom:(NSString *)from to:(NSString *)to text:(NSString *)text completionHandler:(MicrosoftTranslateCompletion)completion { + self.completion = completion; + [self fetchTranslateParam:^(NSString *IG, NSString *IID, NSString *token, NSString *key) { + NSString *translateUrlString = [NSString stringWithFormat:@"%@?isVertical=1&IG=%@&IID=%@", kTTranslateV3Host, IG, IID]; + + /* + NSMutableURLRequest *request = [[NSMutableURLRequest alloc] initWithURL:[NSURL URLWithString:translateUrlString]]; + request.HTTPMethod = @"POST"; + request.HTTPBody = [[NSString stringWithFormat:@"tryFetchingGenderDebiasedTranslations=true&fromLang=%@&to=%@&text=%@&token=%@&key=%@", from, to, text, token, key] dataUsingEncoding:NSUTF8StringEncoding]; + NSURLSessionDataTask *task = [self.translateSession dataTaskWithRequest:request uploadProgress:nil downloadProgress:nil completionHandler:^(NSURLResponse * _Nonnull response, id _Nullable responseObject, NSError * _Nullable error) { + if (![responseObject isKindOfClass:[NSData class]]) { + self.translateError = EZTranslateError(EZErrorTypeAPI, @"microsoft translate responseObject is not NSData", nil); + NSLog(@"microsoft translate responseObject type: %@", [responseObject class]); + [self executeCallback]; + return; + } + self.translateData = responseObject; + self.translateError = error; + [self executeCallback]; + }]; + [task resume]; + */ + [self.translateSession POST:translateUrlString parameters:@{ + @"tryFetchingGenderDebiasedTranslations": @"true", + @"text": text, + @"fromLang": from, + @"to": to, + @"token": token, + @"key": key + } progress:nil success:^(NSURLSessionDataTask * _Nonnull task, id _Nullable responseObject) { + if (![responseObject isKindOfClass:[NSData class]]) { + self.translateError = EZTranslateError(EZErrorTypeAPI, @"microsoft translate responseObject is not NSData", nil); + NSLog(@"microsoft translate responseObject type: %@", [responseObject class]); + [self executeCallback]; + return; + } + self.translateData = responseObject; + [self executeCallback]; + } failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) { + NSHTTPURLResponse *response = (NSHTTPURLResponse *)task.response; + // if this problem occurs, you can try switching networks + // if you use a VPN, you can try replacing nodes,or try adding `bing.com` into a direct rule + // https://immersivetranslate.com/docs/faq/#429-%E9%94%99%E8%AF%AF + if (response.statusCode == 429) { + self.translateError = EZTranslateError(EZErrorTypeAPI, @"microsoft translate too many requests", nil); + } else { + self.translateError = error; + } + [self executeCallback]; + }]; + + NSString *lookupUrlString = [NSString stringWithFormat:@"%@?isVertical=1&IG=%@&IID=%@", kTLookupV3Host, IG, IID]; + [self.translateSession POST:lookupUrlString parameters:@{ + @"from": from, + @"to": to, + @"text": text, + @"token": token, + @"key": key + } progress:nil success:^(NSURLSessionDataTask * _Nonnull task, id _Nullable responseObject) { + if (![responseObject isKindOfClass:[NSData class]]) { + self.lookupError = EZTranslateError(EZErrorTypeAPI, @"microsoft lookup responseObject is not NSData", nil); + NSLog(@"microsoft lookup responseObject type: %@", [responseObject class]); + [self executeCallback]; + return; + } + self.lookupData = responseObject; + [self executeCallback]; + } failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) { + NSLog(@"microsoft lookup error: %@", error); + self.lookupError = error; + [self executeCallback]; + }]; + + } failure:^(NSError * error) { + completion(nil, nil, error, nil); + }]; +} + +- (void)reset { + [self resetToken]; + [self resetData]; +} + +- (void)resetToken { + kIG = nil; + kIID = nil; + kToken = nil; + kKey = nil; +} + +- (void)resetData { + self.translateData = nil; + self.lookupData = nil; + self.translateError = nil; + self.responseCount = 0; +} + +- (NSString *)getIGValueFromHTML:(NSString *)htmlString { + NSString *pattern = @"IG:\\s*\"([^\"]+)\""; + NSRegularExpression *regex = [NSRegularExpression regularExpressionWithPattern:pattern options:0 error:nil]; + NSTextCheckingResult *match = [regex firstMatchInString:htmlString options:0 range:NSMakeRange(0, htmlString.length)]; + + if (match && match.numberOfRanges >= 2) { + NSRange igValueRange = [match rangeAtIndex:1]; + NSString *igValue = [htmlString substringWithRange:igValueRange]; + return igValue; + } + + return nil; +} + +- (NSArray *)getParamsAbusePreventionHelperArrayFromHTML:(NSString *)htmlString { + NSString *pattern = @"params_AbusePreventionHelper\\s*=\\s*\\[([^]]+)\\]"; + NSRegularExpression *regex = [NSRegularExpression regularExpressionWithPattern:pattern options:0 error:nil]; + NSTextCheckingResult *match = [regex firstMatchInString:htmlString options:0 range:NSMakeRange(0, htmlString.length)]; + + if (match && match.numberOfRanges >= 2) { + NSRange arrayRange = [match rangeAtIndex:1]; + NSString *arrayString = [htmlString substringWithRange:arrayRange]; + arrayString = [arrayString stringByReplacingOccurrencesOfString:@"\"" withString:@""]; // Remove double quotes + NSArray *array = [arrayString componentsSeparatedByString:@","]; + return array; + } + + return nil; +} + +- (NSString *)getValueOfDataIidFromHTML:(NSString *)htmlString { + NSString *pattern = @"data-iid\\s*=\\s*\"([^\"]+)\""; + NSRegularExpression *regex = [NSRegularExpression regularExpressionWithPattern:pattern options:0 error:nil]; + NSTextCheckingResult *match = [regex firstMatchInString:htmlString options:0 range:NSMakeRange(0, htmlString.length)]; + + if (match && match.numberOfRanges >= 2) { + NSRange dataIidValueRange = [match rangeAtIndex:1]; + NSString *dataIidValue = [htmlString substringWithRange:dataIidValueRange]; + return [dataIidValue stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]]; + } + + return nil; +} + +- (AFHTTPSessionManager *)htmlSession { + if (!_htmlSession) { + AFHTTPSessionManager *htmlSession = [AFHTTPSessionManager manager]; + AFHTTPRequestSerializer *requestSerializer = [AFHTTPRequestSerializer serializer]; + [requestSerializer setValue:self.userAgent forHTTPHeaderField:@"User-Agent"]; + htmlSession.requestSerializer = requestSerializer; + AFHTTPResponseSerializer *responseSerializer = [AFHTTPResponseSerializer serializer]; + responseSerializer.acceptableContentTypes = [NSSet setWithObjects:@"text/html", nil]; + htmlSession.responseSerializer = responseSerializer; + _htmlSession = htmlSession; + } + return _htmlSession; +} + +- (AFHTTPSessionManager *)translateSession { + if (!_translateSession) { + AFHTTPSessionManager *session = [AFHTTPSessionManager manager]; + AFHTTPRequestSerializer *requestSerializer = [AFHTTPRequestSerializer serializer]; + [requestSerializer setValue:self.userAgent forHTTPHeaderField:@"User-Agent"]; + session.requestSerializer = requestSerializer; + AFHTTPResponseSerializer *responseSerializer = [AFHTTPResponseSerializer serializer]; + session.responseSerializer = responseSerializer; + _translateSession = session; + } + return _translateSession; +} + +- (NSString *)userAgent { + return @"Mozilla/5.0 " + "AppleWebKit/537.36 (KHTML, like Gecko) " + "Chrome/77.0.3865.120 " + "Safari/537.36"; +} +@end diff --git a/Easydict/Feature/Service/Microsoft/EZMicrosoftService.h b/Easydict/Feature/Service/Microsoft/EZMicrosoftService.h new file mode 100644 index 000000000..2b409178b --- /dev/null +++ b/Easydict/Feature/Service/Microsoft/EZMicrosoftService.h @@ -0,0 +1,17 @@ +// +// EZMicrosoftService.h +// Easydict +// +// Created by ChoiKarl on 2023/8/8. +// Copyright © 2023 izual. All rights reserved. +// + +#import "EZQueryService.h" + +NS_ASSUME_NONNULL_BEGIN + +@interface EZMicrosoftService : EZQueryService + +@end + +NS_ASSUME_NONNULL_END diff --git a/Easydict/Feature/Service/Microsoft/EZMicrosoftService.m b/Easydict/Feature/Service/Microsoft/EZMicrosoftService.m new file mode 100644 index 000000000..f88fed010 --- /dev/null +++ b/Easydict/Feature/Service/Microsoft/EZMicrosoftService.m @@ -0,0 +1,262 @@ +// +// EZMicrosoftService.m +// Easydict +// +// Created by ChoiKarl on 2023/8/8. +// Copyright © 2023 izual. All rights reserved. +// + +#import "EZMicrosoftService.h" +#import "EZMicrosoftRequest.h" +#import "MJExtension.h" +#import "EZMicrosoftTranslateModel.h" +#import "EZMicrosoftLookupModel.h" + +@interface EZMicrosoftService() +@property (nonatomic, strong) EZMicrosoftRequest *request; +@property (nonatomic, assign) BOOL canRetry; +@end + +@implementation EZMicrosoftService + +- (instancetype)init { + if (self = [super init]) { + _canRetry = YES; + _request = [[EZMicrosoftRequest alloc] init]; + } + return self; +} + +#pragma mark - override +- (MMOrderedDictionary *)supportLanguagesDictionary { + MMOrderedDictionary *orderedDict = [[MMOrderedDictionary alloc] initWithKeysAndObjects: + EZLanguageAuto, @"auto-detect", + EZLanguageSimplifiedChinese, @"zh-Hans", + EZLanguageTraditionalChinese, @"zh-Hant", + 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, @"nb", + EZLanguageSerbian, @"sr-Cyrl", + EZLanguageCroatian, @"hr", + EZLanguageMongolian, @"mn-Mong", + EZLanguageHebrew, @"he", + nil]; + return orderedDict; +} + +- (void)translate:(NSString *)text from:(nonnull EZLanguage)from to:(nonnull EZLanguage)to completion:(nonnull void (^)(EZQueryResult * _Nullable, NSError * _Nullable))completion { + if ([self prehandleQueryTextLanguage:text autoConvertChineseText:NO from:from to:to completion:completion]) { + return; + } + + text = [self maxTextLength:text fromLanguage:from]; + NSString *fromCode = [self languageCodeForLanguage:from]; + NSString *toCode = [self languageCodeForLanguage:to]; + mm_weakify(self) + [self.request translateWithFrom:fromCode to:toCode text:text completionHandler:^(NSData * _Nullable translateData, NSData * _Nullable lookupData, NSError * _Nullable translateError, NSError * _Nullable lookupError) { + mm_strongify(self) + @try { + if (translateError) { + self.result.error = translateError; + NSLog(@"microsoft translate error %@", translateError); + } else { + BOOL needRetry; + NSError * error = [self processTranslateResult:translateData text:text from:from to:to needRetry: &needRetry]; + // canRetry用来避免递归调用,code205只主动重试一次。 + if (self.canRetry && needRetry) { + self.canRetry = NO; + [self translate:text from:from to:to completion:completion]; + return; + } + self.canRetry = YES; + if (error) { + self.result.error = error; + completion(self.result, error); + return; + } + if (lookupError) { + NSLog(@"microsoft lookup error %@", lookupError); + } else { + [self processWordSimpleWordAndPart:lookupData]; + } + } + completion(self.result ,translateError); + } @catch (NSException *exception) { + MMLogInfo(@"微软翻译接口数据解析异常 %@", exception); + completion(self.result, EZTranslateError(EZErrorTypeAPI, @"microsoft translate data parse failed", exception)); + } + }]; +} +- (nullable NSString *)wordLink:(EZQueryModel *)queryModel { + NSString *from = [self languageCodeForLanguage:queryModel.queryFromLanguage]; + NSString *to = [self languageCodeForLanguage:queryModel.queryTargetLanguage]; + NSString *maxText = [self maxTextLength:queryModel.inputText fromLanguage:queryModel.queryFromLanguage]; + NSString *text = [maxText stringByAddingPercentEncodingWithAllowedCharacters:[NSCharacterSet URLQueryAllowedCharacterSet]]; + return [NSString stringWithFormat:@"%@/?text=%@&from=%@&to=%@", kTranslatorHost, text, from, to]; +} + +- (NSString *)name { + return NSLocalizedString(@"microsoft_translate", nil); +} + +- (EZServiceType)serviceType { + return EZServiceTypeMicrosoft; +} + +#pragma mark - private +- (NSString *)maxTextLength:(NSString *)text fromLanguage:(EZLanguage)from { + if(text.length > 1000) { + return [text substringToIndex:1000]; + } + return text; +} + +- (nullable NSError *)processTranslateResult:(NSData *)translateData text:(NSString *)text from:(EZLanguage)from to:(EZLanguage)to needRetry:(BOOL *)needRetry { + if (translateData.length == 0) { + return EZTranslateError(EZErrorTypeAPI, @"microsoft translate data is empty", nil); + } + NSArray *json = [NSJSONSerialization JSONObjectWithData:translateData options:0 error:nil]; + if (![json isKindOfClass:[NSArray class]]) { + NSString *msg = [NSString stringWithFormat:@"microsoft json parse failed\n%@", json]; + if ([json isKindOfClass:[NSDictionary class]]) { + // 通过测试发现205应该是token失效,需要重新获取token + if ([((NSDictionary *)json)[@"statusCode"] intValue] == 205) { + msg = @"token invalid, please try again or restart the app."; + [self.request reset]; + if (needRetry) { + *needRetry = YES; + } + } + } + return EZTranslateError(EZErrorTypeAPI, msg, nil); + } + EZMicrosoftTranslateModel *translateModel = [EZMicrosoftTranslateModel mj_objectArrayWithKeyValuesArray:json].firstObject; + self.result.from = translateModel.detectedLanguage.language ? [self languageEnumFromCode:translateModel.detectedLanguage.language] : from; + self.result.to = translateModel.translations.firstObject.to ? [self languageEnumFromCode:translateModel.translations.firstObject.to] : to; + + // phonetic + if (json.count >= 2 && [json[1] isKindOfClass:[NSDictionary class]]) { + NSString *inputTransliteration = json[1][@"inputTransliteration"]; + EZWordPhonetic *phonetic = [EZWordPhonetic new]; + phonetic.name = NSLocalizedString(@"us_phonetic", nil); + if ([EZLanguageManager.shared isChineseLanguage:self.result.from]) { + phonetic.name = NSLocalizedString(@"chinese_phonetic", nil); + // 中文超过4个字感觉没必要展示拼音了,拼音会特别长。 + if (text.length > 4) { + goto outer; + } + } + phonetic.value = inputTransliteration; + // https://learn.microsoft.com/zh-cn/azure/ai-services/speech-service/language-support?tabs=tts#supported-languages +// phonetic.speakURL = result.fromSpeakURL; + phonetic.language = self.result.queryModel.queryFromLanguage; + phonetic.word = text; + + if (!self.result.wordResult) { + self.result.wordResult = [EZTranslateWordResult new]; + } + self.result.wordResult.phonetics = @[phonetic]; + } +outer: + self.result.raw = translateData; + self.result.translatedResults = [translateModel.translations mm_map:^id _Nullable(EZMicrosoftTranslationsModel * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) { + return obj.text; + }]; + return nil; +} + +- (void)processWordSimpleWordAndPart:(NSData *)lookupData { + if (!lookupData) return; + NSArray *lookupJson = [NSJSONSerialization JSONObjectWithData:lookupData options:0 error:nil]; + if ([lookupJson isKindOfClass:[NSArray class]]) { + EZMicrosoftLookupModel *lookupModel = [EZMicrosoftLookupModel mj_objectArrayWithKeyValuesArray:lookupJson].firstObject; + EZTranslateWordResult *wordResult = self.result.wordResult ?: [EZTranslateWordResult new]; + NSMutableDictionary *> *tags = [NSMutableDictionary dictionary]; + for (EZMicrosoftLookupTranslationsModel *translation in lookupModel.translations) { + NSMutableArray *array = tags[translation.posTag]; + if (!array) { + array = [NSMutableArray array]; + tags[translation.posTag] = array; + } + [array addObject:translation]; + } + + // 中文翻译英文 + if (([self.result.from isEqualToString:EZLanguageSimplifiedChinese] || [self.result.from isEqualToString:EZLanguageTraditionalChinese]) && [self.result.to isEqualToString:EZLanguageEnglish]) { + NSMutableArray *simpleWords = [NSMutableArray array]; + [tags enumerateKeysAndObjectsUsingBlock:^(NSString * _Nonnull key, NSMutableArray * _Nonnull obj, BOOL * _Nonnull stop) { + for (EZMicrosoftLookupTranslationsModel *model in obj) { + EZTranslateSimpleWord * simpleWord = [EZTranslateSimpleWord new]; + simpleWord.part = [key lowercaseString]; + simpleWord.word = model.displayTarget; + simpleWord.means = [model.backTranslations mm_map:^id _Nullable(EZMicrosoftLookupBackTranslationsModel * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) { + return obj.displayText; + }]; + [simpleWords addObject:simpleWord]; + } + }]; + if (simpleWords.count) { + wordResult.simpleWords = simpleWords; + } + } else { + NSMutableArray *parts = [NSMutableArray array]; + [tags enumerateKeysAndObjectsUsingBlock:^(NSString * _Nonnull key, NSMutableArray * _Nonnull obj, BOOL * _Nonnull stop) { + EZTranslatePart *part = [EZTranslatePart new]; + part.part = [key lowercaseString]; + part.means = [obj mm_map:^id _Nullable(EZMicrosoftLookupTranslationsModel * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) { + return obj.displayTarget; + }]; + [parts addObject:part]; + }]; + if (parts.count) { + wordResult.parts = [parts copy]; + } + } + + if (wordResult.parts.count || wordResult.simpleWords.count) { + self.result.wordResult = wordResult; + } + } +} + + +@end diff --git a/Easydict/Feature/Service/Microsoft/EZMicrosoftTranslateModel.h b/Easydict/Feature/Service/Microsoft/EZMicrosoftTranslateModel.h new file mode 100644 index 000000000..c0f153dbc --- /dev/null +++ b/Easydict/Feature/Service/Microsoft/EZMicrosoftTranslateModel.h @@ -0,0 +1,47 @@ +// +// EZMicrosoftTranslateModel.h +// Easydict +// +// Created by ChoiKarl on 2023/8/10. +// Copyright © 2023 izual. All rights reserved. +// + +#import + +NS_ASSUME_NONNULL_BEGIN + +/// 检测出的from语言 +@interface EZMicrosoftDetectedLanguageModel : NSObject +/// example:en、zh-Hans... +@property (nonatomic, copy) NSString *language; +@property (nonatomic, assign) double score; +@end + +@interface EZMicrosoftTransliterationModel : NSObject +@property (nonatomic, strong) NSString *text; +@property (nonatomic, strong) NSString *script; +@end + +@interface EZMicrosoftSentLenModel : NSObject +@property (nonatomic, strong) NSArray *srcSentLen; +@property (nonatomic, strong) NSArray *transSentLen; +@end + +/// 翻译结果 +@interface EZMicrosoftTranslationsModel : NSObject +/// 翻译结果 +@property (nonatomic, copy) NSString *text; +@property (nonatomic, strong) EZMicrosoftTransliterationModel *transliteration; +/// 翻译源语言 +/// example:en、zh-Hans... +@property (nonatomic, copy) NSString *to; +@property (nonatomic, strong) EZMicrosoftSentLenModel *sentLen; +@end + + +@interface EZMicrosoftTranslateModel : NSObject +@property (nonatomic, strong) EZMicrosoftDetectedLanguageModel *detectedLanguage; +@property (nonatomic, strong) NSArray *translations; +@end + +NS_ASSUME_NONNULL_END diff --git a/Easydict/Feature/Service/Microsoft/EZMicrosoftTranslateModel.m b/Easydict/Feature/Service/Microsoft/EZMicrosoftTranslateModel.m new file mode 100644 index 000000000..c9b1c38b3 --- /dev/null +++ b/Easydict/Feature/Service/Microsoft/EZMicrosoftTranslateModel.m @@ -0,0 +1,39 @@ +// +// EZMicrosoftTranslateModel.m +// Easydict +// +// Created by ChoiKarl on 2023/8/10. +// Copyright © 2023 izual. All rights reserved. +// + +#import "EZMicrosoftTranslateModel.h" +#import "MJExtension.h" + +@implementation EZMicrosoftDetectedLanguageModel + +@end + +@implementation EZMicrosoftTransliterationModel + +@end + +@implementation EZMicrosoftSentLenModel ++ (NSDictionary *)mj_objectClassInArray { + return @{ + @"srcSentLen": [NSNumber class], + @"transSentLen": [NSNumber class] + }; +} +@end + +@implementation EZMicrosoftTranslationsModel + +@end + +@implementation EZMicrosoftTranslateModel ++ (NSDictionary *)mj_objectClassInArray { + return @{ + @"translations": [EZMicrosoftTranslationsModel class] + }; +} +@end diff --git a/Easydict/Feature/Service/Model/EZEnumTypes.h b/Easydict/Feature/Service/Model/EZEnumTypes.h index 88e1243b7..6ff614161 100644 --- a/Easydict/Feature/Service/Model/EZEnumTypes.h +++ b/Easydict/Feature/Service/Model/EZEnumTypes.h @@ -36,6 +36,7 @@ FOUNDATION_EXPORT EZServiceType const EZServiceTypeApple; FOUNDATION_EXPORT EZServiceType const EZServiceTypeDeepL; FOUNDATION_EXPORT EZServiceType const EZServiceTypeVolcano; FOUNDATION_EXPORT EZServiceType const EZServiceTypeOpenAI; +FOUNDATION_EXPORT EZServiceType const EZServiceTypeMicrosoft; FOUNDATION_EXPORT EZServiceType const EZServiceTypeAppleDictionary; diff --git a/Easydict/Feature/Service/Model/EZEnumTypes.m b/Easydict/Feature/Service/Model/EZEnumTypes.m index 41374215a..0171b5adf 100644 --- a/Easydict/Feature/Service/Model/EZEnumTypes.m +++ b/Easydict/Feature/Service/Model/EZEnumTypes.m @@ -17,6 +17,8 @@ NSString *const EZServiceTypeDeepL = @"DeepL"; NSString *const EZServiceTypeVolcano = @"Volcano"; NSString *const EZServiceTypeOpenAI = @"OpenAI"; +NSString *const EZServiceTypeMicrosoft = @"Microsoft"; + NSString *const EZServiceTypeAppleDictionary = @"AppleDictionary"; diff --git a/Easydict/Feature/Service/Model/EZQueryResult.m b/Easydict/Feature/Service/Model/EZQueryResult.m index bf8c97e08..b7eb008cb 100644 --- a/Easydict/Feature/Service/Model/EZQueryResult.m +++ b/Easydict/Feature/Service/Model/EZQueryResult.m @@ -21,15 +21,18 @@ interjection -> interj. */ NSString *getPartName(NSString *part) { - NSDictionary *dict = @{ + static NSDictionary *dict = @{ @"adjective" : @"adj.", + @"adj" : @"adj.", @"adverb" : @"adv.", + @"adv": @"adv.", @"verb" : @"v.", @"noun" : @"n.", @"pronoun" : @"pron.", @"preposition" : @"prep.", @"conjunction" : @"conj.", @"interjection" : @"interj.", + @"det": @"det.", // determinative 限定词 }; NSString *partName = dict[part]; diff --git a/Easydict/Feature/Service/Model/EZServiceTypes.m b/Easydict/Feature/Service/Model/EZServiceTypes.m index 84ce8da7d..532c08f81 100644 --- a/Easydict/Feature/Service/Model/EZServiceTypes.m +++ b/Easydict/Feature/Service/Model/EZServiceTypes.m @@ -14,12 +14,13 @@ #import "EZVolcanoTranslate.h" #import "EZAppleService.h" #import "EZOpenAIService.h" +#import "EZMicrosoftService.h" #import "EZConfiguration.h" #import "EZAppleDictionary.h" @interface EZServiceTypes () -@property (nonatomic, strong) MMOrderedDictionary *allServiceDict; +@property(nonatomic, strong) MMOrderedDictionary *allServiceDict; @end @@ -47,15 +48,16 @@ + (instancetype)allocWithZone:(struct _NSZone *)zone { - (MMOrderedDictionary *)allServiceDict { MMOrderedDictionary *allServiceDict = [[MMOrderedDictionary alloc] initWithKeysAndObjects: - // EZServiceTypeOpenAI, [EZOpenAIService class], - EZServiceTypeYoudao, [EZYoudaoTranslate class], - EZServiceTypeAppleDictionary, [EZAppleDictionary class], - EZServiceTypeDeepL, [EZDeepLTranslate class], - EZServiceTypeGoogle, [EZGoogleTranslate class], - EZServiceTypeApple, [EZAppleService class], - EZServiceTypeBaidu, [EZBaiduTranslate class], - EZServiceTypeVolcano, [EZVolcanoTranslate class], - nil]; + // EZServiceTypeOpenAI, [EZOpenAIService class], + EZServiceTypeYoudao, [EZYoudaoTranslate class], + EZServiceTypeAppleDictionary, [EZAppleDictionary class], + EZServiceTypeDeepL, [EZDeepLTranslate class], + EZServiceTypeGoogle, [EZGoogleTranslate class], + EZServiceTypeApple, [EZAppleService class], + EZServiceTypeBaidu, [EZBaiduTranslate class], + EZServiceTypeVolcano, [EZVolcanoTranslate class], + EZServiceTypeMicrosoft, [EZMicrosoftService class], + nil]; if ([EZConfiguration.shared isBeta]) { [allServiceDict insertObject:[EZOpenAIService class] forKey:EZServiceTypeOpenAI atIndex:0]; } @@ -72,7 +74,7 @@ - (nullable EZQueryService *)serviceWithType:(EZServiceType)type { NSMutableArray *services = [NSMutableArray array]; for (EZServiceType type in types) { EZQueryService *service = [self serviceWithType:type]; - // May be OpenAI has been disabled. + // Maybe OpenAI has been disabled. if (service) { [services addObject:service]; } diff --git a/en.lproj/Localizable.strings b/en.lproj/Localizable.strings index 1fe37c8af..6eabffcfa 100644 --- a/en.lproj/Localizable.strings +++ b/en.lproj/Localizable.strings @@ -93,6 +93,7 @@ "apple_translate" = "System Translation"; "volcano_translate" = "Volcano Translate"; "openai_translate" = "OpenAI Translate"; +"microsoft_translate" = "Microsoft Translate"; "apple_dictionary" = "System Dictionary"; // disabled app list diff --git a/zh-Hans.lproj/Localizable.strings b/zh-Hans.lproj/Localizable.strings index 94060d397..f6a4a304e 100644 --- a/zh-Hans.lproj/Localizable.strings +++ b/zh-Hans.lproj/Localizable.strings @@ -92,6 +92,7 @@ "apple_translate" = "苹果翻译"; "volcano_translate" = "火山翻译"; "openai_translate" = "OpenAI 翻译"; +"microsoft_translate" = "微软翻译"; "apple_dictionary" = "苹果词典"; // disabled app list