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