From 4678bcd136e6b56ae64265d2188813eab30b8387 Mon Sep 17 00:00:00 2001 From: ChoiKarl <253440030@qq.com> Date: Tue, 15 Aug 2023 21:44:14 +0800 Subject: [PATCH] Microsoft translate (#161) * init microsoft translate * perf: microsoft translate result * optimize translate result logic * microsoft translate show parts and simpleWords. * microsoft translate result add phonetic * code 429 tips * from chinese translate, if length greater than 4 not show phonetic * miscrsoft translate token invalid, reset token. * when code205 retry once * improve the logic of wordResult * change microsoft icon --------- Co-authored-by: ChoiKarl <253440040@qq.com> --- .idea/.gitignore | 8 + .idea/Easydict.iml | 2 + .idea/dictionaries/choikarl.xml | 11 + .idea/misc.xml | 8 + .idea/modules.xml | 8 + .idea/runConfigurations/Easydict.xml | 11 + .idea/runConfigurations/EasydictHelper.xml | 11 + .idea/vcs.xml | 6 + .idea/xcode.xml | 4 + Easydict.xcodeproj/project.pbxproj | 40 ++- .../xcshareddata/IDEWorkspaceChecks.plist | 8 + .../Microsoft.imageset/Contents.json | 21 ++ .../Microsoft Translate.png | Bin 0 -> 27037 bytes .../Microsoft/EZMicrosoftLookupModel.h | 35 +++ .../Microsoft/EZMicrosoftLookupModel.m | 31 ++ .../Service/Microsoft/EZMicrosoftRequest.h | 24 ++ .../Service/Microsoft/EZMicrosoftRequest.m | 273 ++++++++++++++++++ .../Service/Microsoft/EZMicrosoftService.h | 17 ++ .../Service/Microsoft/EZMicrosoftService.m | 262 +++++++++++++++++ .../Microsoft/EZMicrosoftTranslateModel.h | 47 +++ .../Microsoft/EZMicrosoftTranslateModel.m | 39 +++ Easydict/Feature/Service/Model/EZEnumTypes.h | 1 + Easydict/Feature/Service/Model/EZEnumTypes.m | 2 + .../Feature/Service/Model/EZQueryResult.m | 5 +- .../Feature/Service/Model/EZServiceTypes.m | 24 +- en.lproj/Localizable.strings | 1 + zh-Hans.lproj/Localizable.strings | 1 + 27 files changed, 884 insertions(+), 16 deletions(-) create mode 100644 .idea/.gitignore create mode 100644 .idea/Easydict.iml create mode 100644 .idea/dictionaries/choikarl.xml create mode 100644 .idea/misc.xml create mode 100644 .idea/modules.xml create mode 100644 .idea/runConfigurations/Easydict.xml create mode 100644 .idea/runConfigurations/EasydictHelper.xml create mode 100644 .idea/vcs.xml create mode 100644 .idea/xcode.xml create mode 100644 Easydict.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist create mode 100644 Easydict/App/Assets.xcassets/service-icon/Microsoft.imageset/Contents.json create mode 100644 Easydict/App/Assets.xcassets/service-icon/Microsoft.imageset/Microsoft Translate.png create mode 100644 Easydict/Feature/Service/Microsoft/EZMicrosoftLookupModel.h create mode 100644 Easydict/Feature/Service/Microsoft/EZMicrosoftLookupModel.m create mode 100644 Easydict/Feature/Service/Microsoft/EZMicrosoftRequest.h create mode 100644 Easydict/Feature/Service/Microsoft/EZMicrosoftRequest.m create mode 100644 Easydict/Feature/Service/Microsoft/EZMicrosoftService.h create mode 100644 Easydict/Feature/Service/Microsoft/EZMicrosoftService.m create mode 100644 Easydict/Feature/Service/Microsoft/EZMicrosoftTranslateModel.h create mode 100644 Easydict/Feature/Service/Microsoft/EZMicrosoftTranslateModel.m 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 0000000000000000000000000000000000000000..fb196bf1f6c0b7bac8ba1c4e5c335dfc29e9cc4f GIT binary patch literal 27037 zcmc$`cRbep-#30#l9F`VnJu!Zh!9DVEgZ=v$;#e)g%e3B9QMi~gkbe>G(O}8=+dP9MKI8JBIVbs)~t7v}hcAA1Ex~FJqAJ?r`b$IxP^u*7P z;Vo09hg_*U%kxWR%h@~o!pr4(A_w=!%oNewk(nco>&|nlqib=FBT-v0F*K-R5wZGtw|{t;;gvi6 z?m><3sXAua2!fOey@dk5{r&y1WXa%$D$lLoW1llK%?9@yEKZy_p`WBgv;AbdREd?q z*Vp&a)S8>9*lI;hO^sD;Urk3x$G3K2A)$u&YXW?Hlgs-eXX>uCl9H0% zhB7cPq~D7qr`njkJNE_|23Ac0BLP4L&;9DAty z!tyfHw!~-S&g9(2lLnz;PQUd&KWhqG2|aa=o~X2}tWiv%*wc{nR7(UG$MR4S&#&28 zw&me6fhU!2&d%-ecdm%(=025l{F%* z!90YGOrCq+W`>F^nYNuREtj|46Wlik^oC3Aujq>Huj3}DpYHE$06`H>_0b7f#BNG0 zbxJWYY!^GPO=ozbm^`+|-iB%xzL4HKQDQ&en=!gPKTqc@*EKq}&9`E?gE93SGTPA; z#f?s!&e)HiJak6ot%@lPb{SVk@f~m+*2Vr7%WrN^HXkaKgy}z(Z@+>~$Fxk@Fboe5 zZ(k%j10)Y#+uj==8ym~k;#PA$rB%Xp&L^=wU3Kp7Emih#!W7mFH_<2T;q1pMS2uqCawjDax8f!8Ie-n+m1t#)5&1}j=Ufvx z>;Osz2aqyxZ~plcspGLd)fK^|*>LaRp;IPVSMzb2#Rqy%#Xk}x=87#C)EKSLTkUQx zZjX$NI6cH)Ee726#nx2WApe9%RJiRV=q-!0ooAwlw2#0s$M^s`PS|+4j^~}pu z(4^=51&+Do+n_@ z82(06F`r$=;+G_|VU&MQ<+OOFe?Wk%ZOM;$nbfW(eVTYie|?^*kaFrIIbV`;5_DIWZ!}GuY&JlW5zunS1aOW@=`7m zWuNi+8PS<@4E0=eb>B`^0T1rWE~)80X)V*M z;^$Qdhbjt&70FO{&l4mt(bJbNCj~INWaQ-Jd|K_UB*1R+a8ho7 zf|3t^EL|f)B~ne$n99}goIOdUJAx-`Rp!Yt z8oE7+b}RMNj$jezW|CQVN*u(*Q7EpZySY!wr`}32ZqH?Fmo@vS6cWj0Wtu9LE_`W; z;MA?$oAJH4WsE=TtJ_+fRF=D&MOFJ!qq`%2WPDU^!QeDgC83#ETbpZfc3nm=aT9)G z^~cnBnJFc=OUq%R&eWa_aYc*`&w|^G|!}L4>$KdaB6e>U1 zvV3o~MZjZ!UU$@eB19k}^X1i~^gsgabb4v1vSHo8DMqKCFYhJ&R-*AYD(u`wp^P1? zHo&?avo#EPFtrA*A)-fE&2S7xxLe`XDC!< z@>y?vy(VfcK_Tf$AO@Gwl7P~Dd=(M3$wbNrnmLc!`Q&o56o`*G8vId&X zrQLNTZ@rl#XD783D{*09EXO}I}#v~iLtHh=A_DB_crWp3v zT^xq-p`7oj&!Fzs+0EFLBouFr2iL&@qRE5ZGz!1=l+njgiGLM>AF-SaDx_GD{#_L_ zt=N;%wL5xvkh<(PBa9A5p)iWF!otEFJy^ZP!Gcj$^9-hIE5Fj&kD)r`hC=CY+l;{$ zc7057dn)=uLK!kB6yH!+XMubwtaOkCU)zjNKi5z?qcfz*wzJhJ=3xS7)xF@%fI@L{ z4k2#G&4Zt$2R{>GsFHrhaQ3nTkj`sj+fBNEPi(EJJN*TtAIiGAxOhx+Svt2Q z03BBANX4`ib{ajptiN>0equ-9W!tv>&YzrG-aT(BQ4bVq@F76NEYp4`(|(TYpCvBy z;nIRnU3>!Ua_8D*k+Ur?3N-$I`gd;1nv6#82O8ssLbeDFdB(u-sR5urU?D zzAE~%DOU;!NRnQkyLisec6>-+iA=%^uZ%LNn!>b6snaK+<@h|%$)C8+GU&bpXOQxrf9>8uG}oEL=;LXnD;EtV7c>JDx$xhx_Qs#n3g<&|Pp8_jPf>e6t*Xzu|0;T@?MaXDtb5HiexcEPhdR1u1VyL+KfyozUnskn9mZNJ~1@xJ4_N$&*SedZmrp#W^PG|i&|Lnp(VgHtV$YPDUK z_aCh%P1Dn&P~B>to`=q2+wa7-Jz4mCiLiWL#`Dk5FY4%L2%XF!i@0SzNa4Aem$0A0 zd)CsTEB6@c?y$X`-4B;~O0~WH7M`lT)|WExo}W({yv>Uyk#!ulSKDe3sLY+%9I`yr zpz&AZpY2DX67_9Lez=YvY!-Ps&U}wMyv!%xo9)e(TT%yJ+A4s}A&vjow!F|SK#O9* z*Q53uJ!(vDv#F5>Vo9^31qOODXJwhTs!x@2mrRCTtL4=i{a|nc<0?IcW61W)Au@X179+5H)<@5pJd_jw{vJS?`M{T{@)M`di8z6`XA|B* zkuPg(8u{#r`O?%w!`XKUdy~1~2Mxv8X`xUZU%)%*OJOZ_gOC6b)nbvsr*5V(D<_8? zL`=O6J{EobydKr=HeC#cWQJ-)7KAQ^+J8(lxPJTuxbNwFQpDug39et4Tkj==Mbbow zZsvw=d;-w|-*7UwoV=~0nOLqoyK_p$|CIgiEr?6byutMbE0phR*VO6oIwT@@}5-_ zK-hNCHks9B!oE*i?V!(dzt6Hz#aQESGfakmYIAH$s%)f#mUTVl;duga%cB)IB3s<& zT67OqPwnpPBnQT&rIp8@q>QH0@MF8q2?S(zY-DiUGo%uMPvPx^)nt5 z(wDeTUSSG^9eVEDKB5@zJ{o-ubn-AGGka&^UeafQB-ariB6;FVyv+Wonc1c69zVTL zi84rbhT>1ESJOPRCgw0`lD2Q*==i6`k2d-XBj)*8S<*Y}9RVP`?f(qZ{PL_RAoR{= z|7brzsZwn;RxSYRM}cXmQ#RzDT0T5jw$v(jT1ntvLByWnK7@It9uRBj_E)$%Zr4-m zekz|_y{C?QKZPX?)W^%sczk@kr|~$VTEgmwouMAI2*bh?*H+;6qGC* zfeo&5w7^d8cdaylSZJni2g|;z)JIul*Jx)n4j|pCtik zQMK7`5E|~VIEY^S&ZTYGtbq%7`)&XXoyU3#)8f+H+|I-_6B!_@Z?;)Z#b%48+XUUs z0fOZ46DnP*<;~X{jlfbKzCc)qVgn1 zih{d`RcMR(I901c)yo?afYsurwj9iFQWZg=z9sUW%{izT-K_`)r5n$EZjmL*pVM<( zWxO=CSsHQ&^KB4c;_&2rU5hN=ST?ee{T-H9B$4gqZM$qe+=W)trW~c)ji)ZUHE7wzbDheQ` zn9>X>lF^g-#En)M@6FO21wd1m7ZF9F^ccCcOV=}(T}OA}7@3dFO*0-7$py|wa2Nmd z*`oFsqmFw|ogU3>X?ym>^yI*dc6ODu z%DpKb-H~RMwP*%R20HSAXw~MR*#wWrN^oC*zLP0lgg@pjEiH+afMdOiKCUv9qc0O1 zq*hX~JG}he-ofFZ2(Lp*o+X+VB~#?#KGGZtZNV`c!I|rtOv1N2Db=*z?yjea?L#ob zV==x0EG9YMxDwNsqw9GHF$4eag}io_{aWlx>~l0GZ0Az|wU(9m`mCa7sc=I3Xy?m` z;}A;x{9)U^zvw2)R5aLK{`g59pNWu;qobqUi1W<8!@E#ouy=`quXDEJMz`bokTK5* zupg)ywOtnr9!*^Zk&-8WjSNZ#Lm@W1u<)t(dX5Pi`&_^sW0x5l+bnH5ejRouuRQx^ zf4klDpq3-M_~~4Cc_1xqJS_tTd&`AJ?6knBo9&(-kK5Q&^RLHHiSAiXa>qjz?re0Y z<5r^!1yrt_P*J`?Z*pY>ha=|!)d;0^0>QdFukNr6UZl-hn}8JgJrx{wn%|5B_w`O> zEkwmzBd*K)cMV4~Sm?n*cHAp1EnO-DQVY4dy4F1x@G@4_lX3AL1K%YXG9TG_fZTnw zcAPM6aM>(2W=Xxu!yPyDO2B{w_hCqLWHsWKKb^%^4Y!653dQx$=-N2(GXHKFfedIz?MrTB}PCn+AmMmC$ne znuoJYIGgda@btIxOe~efZ|AzEB6SaJNp|*O_k>e6C6$VvJGd71laXvHYb+U>P8mje zG8x<<<=^Nmijy)^QYNn0@4?vW{Jvn$93{XaH`-$c{3M|>ezlEfwcto0 z969By*S>=TW4e2z2Ym%5&EVFy??JDe65Fbwdj$#Xi&-)?Q}UKBD%mo@D~a)FtP26w zv?q0?OFc>HZz>+0J>9wAuE#)y7t%cD(ni$Ts;|DpmVR`^1xTA8j;vz^>H*3QOY~{D zufKs|8l381Ci~7YiE5dX4+SQf=$z>vo~9NmkzRKJsEQ3ZJJKe?bUn+ zM&;xRf|rB|50-B#nk4df*7XJ&iG?#J2LeW4bTotgeK~ayL2J+3ht7?E%0f zhQxx?TNLN9Gn>cniHZE({f5msQrkg&J&Dx)*Q(GyH!{Y~-hO|5Ugu9~ptlKUU#n}l zN#${sf*bw)-ck^>1-iJnh~8AJLbT1YlV`t+Cr*v6^_jJd4sR}J;nVt{Uy+{Mk!k_@ zTcts;4KYOV7Lu1h6Mx4eLK+hmfR)51%| zDP9{tylJL9pTeN$thuGDrWAK%nGK^SXjgSHVw0!W+QmHQ^2_xqqs6*YvbMf?>p(=a z5+JrWMm#;c4co%bg8ggq_VzZ_H_@S&hr{u{lhK0&aA$5KqicXH2wC@|gUa0{SH#F* z3u!C^`Q;3-1r(l#yQ8BQ8dpeLacf}1RqYY%?x!31?O!azeS0ngr*#7Ns*RD|l}8)= z?U|BTP^$_+})X^FNvFasU$WwRWY- zimmRgwYTT{n?7Z16h&6<7ei=CN_PP^d}aSz=-#*4v^a;sZZ>D>lL@M?v9dY^`;o~YaT3AcoPWpf z+@vjE1zecekFXa1N_ zEa^m%jPT0DYvCn#f7p2kK_^_}l-P(0>`=;rxb=$Y!&^5DY`cP4$VP7&ft! zuJ1Md*O<}0>0h7|E-7!3y=qcOUaOpyw+zZu4L3a}U*OP`si6%i;2^ka;dg^cG-}%f zMnwvlRNgC@oarJ+OEKBu)fpFpYZ*-Y%&3Ji49WWWDet?`?mA8}p_ECbt&0Ggp}oQ~ zVo|Zto1KCg0h;sXJkHi~9zZT+RLoXN?V9Lr9uTYL=rCwIPla1rIfLdte#A=Q@f&iZ z>AhP?-11CheLUvUA`PeeYf+4I4S?B@twcO3~&)b0CI=f8bj ze4c*D{oZ-MjeR-^2W`*&4YR7>emo-Dvn6lNzi-Z~C>{X7+K?p|BUKcd{Qx-!^Bb=* z(krPmzBOXIPnAUTX9`@yGoNjzkJ8aqs#H1VhjJ)xwO^Sn$rw%>Z1;J&+rFL`<)Pa( zAGOHeKC^8$d7W=pqt$^kyMDpI8EZ@HNU+^-4jn+XWGNQ*U25Ov`&e%t`~nWz_i#F>@XGiIdM%d(=wJBOIS@c z6%lU7R^PWrg1c?HTQ9ao#P)Gweks02f*^$ruOu6HOz*wNFyH}c4ljREZ*5Qb|Mrr4yc ztxu-vbrf>jC&wV8%O2AkKd?j%?w9c$xb2IKkV#DsPfd*YsVb|vlCH?OxxRj%LoK`f z2gCv>4W)H&Qa8Ov;j>OE%>HR5s+a~w>ZOqi;a>|26c}&`B9JU@PFzwcpINvGwwiCF zMny$kHJw-T(YlZXz?-ooNW7>$1Za=K=ae%#ROjE9OPeepm^*?EG*&@xB&}1?imt0FTTyM!@E>n_+IzB3Q&R^uCT7{R7dpbvdplHHXtB}>VuyQcaE&*I7*=#zO(L&%ijt zo(cOl^v<0-V_ytgzVji(;Vedy5(S^MX;rv*mLL67Blai45}Om1+am9p5Fv*`Cz^nI7k8m3$eb#ol&oiZNE6o%>3z*sV+tfzvC< zU@$uFtu$WCGLTW77nQrBn_%Coo{N979>QN8wb}YN$tois_HGV|%As4(MzPipY7orL z-qD%|9}8a#@FiJ^^%GCH%v#sFeuhbsNu58*Fam{dD7W1_>cD7+ZR*IpI8a(^DByVf zi3t2-HM|9R4EJZa&E-+uczqciw8{zG3$A+K=6mVk9BPb42=On332#rnYg}!YGmB!0 zG6&bYUCIIkiMXt^RDl;yoq^B#64?=VFh1(Hvg5Wkwz9q`Whk+h47!#=4)f@Gf9 zulB|83X6N30~2=T6kK#X)&q6Ck(QET(DUoMB8g038`JZ-Fg5cP6+_&h51!)k^o?da zySpkUQ?HW)F^Jg=)4fd^)b@Ee*6XweE9{!@~OA+lC_JDa%8zFaiWh+2|Xkd!tql| z!hOA)h)aK7w(c2ag{GMG>#rx!H<6$t(Gc>$g$Dkg`OM8#21Cpmnc&*by1WHKrDvUi z*W#Z%%=I_fGs-)Ol|F5@JW`==n0F0N{AA!hcf3`<%AaS^SnL@bBsVn%7EI#sMCb%# zmB)GMDw;%IdY2b%zNf@W@xUb#PXQ_jvF!#*?cbT+wYAvcZEFtaFe`;Cp1T|nHEKn) z&otV|7d&vxE2K;?9*?bT#ulXCazr3f5^mA+ZrCmv-^!ME`BpbmQXjBYmxGS4UfK^U^?=t=41-O_pTA;JoN#IiyW#dX%r$IuY zv^l@Dclz83Fxj^Lh%%t9>3QO;e&tcUG#vXiOtU+UnVAB_w6a4O(9L=tZd;RHr{ zdU^|#t^LX(AFWALQnQ?9793B)>E!ikHt7Ye5@T67PqLLx%4=+O4-Z#-vj+#@3;Wr+ zDKO>Hv%jvL{FqOP)!Ufs311CAgUo3Hx!m*&S@9s?o!Iiy((5yRK1V-*<%k`;SFRzu z@^(2joA+c+c+Nr{0q|8LNeSb$Wvfxgw|H6zu4ZCH1_B^G_AUSQ>(^hu|5T}ZTUx6+ zK%(Y{d|9fTPQfBq7xTQ(9n(N9fo^`xj)-Efd{me~N+9H|l%lVmmGH(t4+Sn8ze8On z2K(Zf&k7?>>ctiax1YC&PaI>n*UM;KBS(n#i7ty*emFSYG@K2*x0+wHEb@>e^pb;` zi<|tenDxAF^ZFAitMJuHy;^`ndw@ezXBj<2F~KV{>rLdtVzH#w^u)+cux-4|mc1cn zMsgaDDDY~61s7eR?amtRaXDGT(JofE-MT#TaorRcJlj5HD(lO<7vcrsX%>~k6@CQ{ zdT}wg4b5aj$pX8dhuvpPC4LPt!(GkH-TlamNX}zcifKcckq_4{(9nET z#SkC^aW*AdbE(qOjLzmF$leklP9{%gOBkuCKmx(~7_u|9vn ziJqwsAAjeLCriuB8o6!9D~7dvuXK^xMoD0zn;*yA+y; zsd;oh;TNSyUv2BIe!q3mydKPP^i!6Kb)4kB5u-|EO|7IHGR9MpESbz zL3u8^zB35Rrx_M$fkNMB*3#=JAx^xO#vOSQBp@;C!$<&ILc7@x%%2~gkA}+t zJYV!KVfqTkul%#OC!7(u%2!dxRRmWIDE>L&BMSKil&C?0iK(7Ud0sWb6M{z+;a!!l za;y2Sv!=$k11I0`rz)Eq2xMoas@;ZdW=6{*VwK265nR8$90mhEpZ{tt!lMmZgQALc zQN{52AfE^j(eQGxj1P?Ed^m_;XRGhP@pm!+A{~YJ?G-+q(16hdM?aoG zblR8$kCs>p@3z zUj5I7k9L=s1$5zEoTo(es*tSF`u5T1qs{&M3I6-K|82^Rqi zlWzM4;YoftYmkhIZq9%5=O1>~i{fPhZ&`Rr|6MfjU%jcu|NdVj;J-hTm}SfX%KzrB zrZq5(0I@Kr(}+x>$ncnZRY@NaAUt64lA4~DOCpEi&$KJBli~Q-Ar6D+z>(nmAIbMW zAkF_JCI2smPsY;FUed^A|K+r6F79mHvF-2hm!$X)S7p%MVO=ZHz6-JC<83v$!M$`k z9S^8xTzv#r9U^va)EfWo)H8R{WzA4riCcKAL&1hYoTl&4lRvH`TW>$Z*4L)Q*h!r3 zwC_W`b#MknkS+%a|Ii)wEju?{v_vG=3C6~Bz_FSZIoy1h)!V3va zw#nMWo79B*PiEA#xe05O4$YXnH~(r7@5sIS@sR1lU{v*e36ZKQn!R=-LZ7<2%jEgN ztY>|K`=)*KUe6P)mIzfvT@axm5IzthI(Nt1ON}ajE2Cv#xTu-fQ5Fv$6NWg zk#K--6)goOQTr3I@6Y^rLDmT2xx*k?33036D;N9!a5PZoze^$|6yMh7zHM(vLLPGI z+A-7{0I3jt)9Vo5T;=VgL4@+zo9g=ph(zLlO9E4Nm+-V4pFd(|U=V;TsF?9t#L$1c z=pERAz^2z(GWZI?;Tyo7>xfk2e?vt2qAzSw0+1XTgh)FPOokX9{6dIK|CjmyPn$qA z1}3X}FY?bqa!n`x>-_)!O(bd@e@6>je&byK^tyeaGy}#T{*aJN%l#lkB}%^37d-S% z*W!**1?&DyhiibvUXPZMkqUv-*5C=JgA*zxTHF_CTce6e;I0I!1_3I#o%#F`$j|?q zHAtD-SNRk;-yaZ*|4c`lU9SFVClZt1$l<1BK`fP*!xbRx1Auw1WNx~?xv<7Zo$KHa zP+dMlh(9;9fTKPaEN=lq~RPu zV_FQE1m1hP^G9?#!Y#_3Q*sn-9v0OZ1RQC|k=apC;zlLDjh6FdVI{?AWGdfG;X zX^;rxm!qe&rUQNZ;{UI`{D%sHHw#(1iYbtH#QFG_CKitK`TtAAXNje_X-V)C1|OaJ zKb{6o|8qFi|J3&*x2KZ^k>NMux3W{OA2A$v>L0cL&!_%15!EC1aOIhcN8T6S57a?U zdxR+n_y0q~@lcZ3?QLRg{7qA5S^>ckD$s*0DZB!=O5sjfT1M#}0T(x*3hHzh0ZR|d z>-a|iT*0hV!Ra$%>UH;zL?ig`3Be7qnKTl)xfApZSBU-#jpTs0{{^`cCHsr1Kv`9~+vv6mWNZFHG3qd#okz8vO;)~}0{WMn)1l=?P2^^DyTVs#J|JScyf8!Y8 zP6?qfUa|+}hoeQSe>?%Ji_jUD!w0f;#_wKY?bleu6vLO1EqE_Ss)ZJhB}Z;fOkXwM zN|P!ELx&)M@n7};I#QBIBnP$wwmT?8A-SPmr`j^k3h4Ps1<|6x=HI`6@2O!R#059p z7JKV7C_KW1Xi;#`h2{!L-yz!l`AsWLJi}gNN>Q_6CA^#vJT&`HV-X zg8)=w*3o_t>L|)X>=<#C@ZG6U+%eQ$vxndK(WAK;umGZUZ^Qx0l9Y(>$Ns|odg6AD z{tfG(9o{cT2zDg6#(zP3KsxCMY&C^{5}R{t$iMI=yv6X}MZrx}cUH~gt0W;LJw83* z#Kyl`+}Fa>TgOlPr`h({e{L$VoLL#)~eUgvHH()X0G~zP`O8HK(GV!S~+kZ4^2F ztNX*pk3ibu2zlTLsriQ=#5DD2J;Qj<;`w*Cm*KX})s4Nh57nw0rx9fInBv|2`#8$0 zfsTd-qoNnRA&#BAZiFaA$K{~<@keTyj4tNT+X(9tC6R=WsJFCV;L*SBTV*55P0};9 zxHap({y91Ml)At_b2$Vgca%Jws1!B3I6>$uQ1JL?`SnP{=P_jJ&NiHkCnbtv_@(6C{sS4LW=fH zL6IjK(e4{&#>OA96^1yt+EM9+RYJ~AHy~)QQT_Y3)#1;dKl{B~cew50ZfCIu%HdZF zBTEpM#fwTQotNb7s~0>m@6B1^S6tK3g`vX1MA9Au#!+uxiEH>QqbJ)7;`=KkRcno4k-ZJ>Bq> zkLg)X1wZ+qWr|fbLrz%B*c6Mfint$0T<+LJrvf>d@alY=AELp({HS$kXznbmb~+$; zmRI#@B2Ez!I?!F{Zaq5HYX)I|oWnO_Q}Z3ASv65UzV#qt`8LFHO*~n?-X&XCKrcDy zj(O0G4)>S>{`XKx*R&qdP}pICHH5MLs|SfRZmpRgjXB34q$*_htHa|zPE}|*suMr> zgS+Nid)RSYcRv49IARUt-cPcarXjn22UPH8i|KuQb9TmrLvDg}5TqfKn~xu_oK2v4 zMLAD$pDx6iM6=^Q?efa+9>9*zl|c&WZZEiB)KH~;W91`c+*D4ejAIK75~_aJ68kg>25a_+L;lNNban9)*Y$-vIb@@vK2zFH=``yTACGOo~G- z<&(~TD9$$k9IMhD&~%WoEz2NTZfNagwjBJE;5;2AGrFo=+nywZfK>F0!-IXKouI$ZRi~6wmWYCWqd|MdbRCRqTD*=f{97xDJEsPaECvS|ufbp$p4efBEZg{~oJrr@7Gm6=7KPg6_eT?rXW*KBjQbBaLIW zR6b4zccD7&8$FBMP!aux85;iR;;o9)sX{)$mHWX5sA@wt4I&ib0x_E~l? zz@#DjFXXxp-0(It>2^FdenHWJC(<=pBj=Z7+9lT%6Y&KkLV2Kk^E!2GH(rbbE7#(d z7vhqV-}f_%w7oAkQM@H0qU8Zg9E1`hDU6!F%FP1r^x-NTy+fC%9C=l|gX_WeObfJD z9>(KuJxgs$+hy1jxgwn6@!Z5>@H68ubJ*~kj~dLeKR*Zdm4(W_NufY3!NCgk5sw*aa{Uw zTcs{Q6}BV?;lU56z@P_{_VW>9u#F9&SVG{>HU9hJCrYywIas7|PsuPmp* z^}(LY-sZO}q2=SruvFIE&l+D(xFH$T^G|oWSxB9bE<5ryCv)UrY5BmS1Bp2`dfsQH4{O$-bPq|XGl`(R2#m~=+F0)l8 z<4|*GL0aiFulTCt13~h=39*BT*KozlEMJ#-i_0=BDq55vPriMAd6~y@v~oX3+v#~K z67tFaPUUf*t>(=7Y6b=4?~47?yVzi+gJmYiF{E_kCNJ;rt%Ln=F3fZD>|wBUkgX z^fL-cq1y0pbR^D88a?95=&5LrcFe2$dx4bOn4tda=fCMfnnM~+Ubwa*lyZH#q18!p zLjGk_O7Z=Po*(lxk&RI_{DH);9*7f+5mMN{sb>z7J^$B-7;KDA2DNeYi0N(gTC4)~ z?ecP?% ztp?Mt$OENQ=SxSv4S<`Dx)`|tmf8$@BU)KF)hPqdt(6Hfmffdff@9d% zo5rls|K<6yO0KRV$=yPXiLj~8yNp_j#I`Ur(e6jF=`xX?+Mv|B2$N7-W z@27I3>z>iQCo9jVq<%b_SW$6@@rhm+LUxPA&LM&h<4xIbk|!~xBAF^`&=XWEpO=!$-*Y+Rnku`DAWWn|4Dp#91w1YlB6QJNR zoaa@yXlrX*kz#cI5PcI=26eZPkDs3lDk%+KCQHPZ)ECbwH?`2SthqOv!!bLRSl3S) zK~RM9deNV6f)hrg(F(ymrP()17{ac7pbn=KTVGPTc>cWW=7MwJ(S)}|_I}reKoMv2 z1ACoJ*;+$Q>uu4@T(8`W40iYJ)hW-hBkG#KkQn%5FR3Ff#J6EIrZKe-MPS`YP9lus zN1T!dpm9Ot_{tuTT2Oz!(`tr0btyF;qcu%?>&27u)YK`%qjN^BLjj*B7E|@%jAFKP zRNYm2GL7U4r-hVIQ5WB%U14b!zv==zEi!9g*FU_iUz#;(_%MEZ=aD#@by;dE^8r-- zFHRlpj4dQ2M0R&DP#7Jltf8-blG*v%Yue-@gT`}%!3fR1VW3j)uxf73RQr*37>E32 zZCec4`%6vb3|JD-F<*if&}X@zQ6Q3@~8d7cx}H~E3d%1&m=@|W*HWGw33p+}Dv?MkT_+e>C=>lnK_I!}rQ?#K? z+x#1mGG{p{l9tSj441H@LkdA>leVy#nHhR5{Kk!|iXNAS65kg?Bb#nxJV=Fn>;+`1 zuGESOcXN695L3Z;?EsO!aF!g2RS}XFq%_h|kUsk#;-7xoQ67jzpYYXnZ?R{U9o|Y!%30>SCEm3$#MB8bPBL5~F8XBs2$=o;3!+cdZ zpoyK6fil{sM$c`m)(^enB=YqLKq=WdIGC7t^}L3Si#!~^kxO%HnN^z8MUcf{O;!d6 zHKL(B0(cS#U6z(AhK8vYf4%PMCBjKV&&-!3NH=10b^_a#XL z+7Q7C{0iE=AsWCYjvkkJQEDe8CCcJt9^U5)@QzyNQOp9%U*R5$H!F&6`rFX`Y=1mnucr+ox#${biwSpXCoOI8S7B_zNmeqQaU|-eTTWFrGvL@`}4`# zn`;ZEceVVmfXH9wV)*3}VN{dOg1VIZ&3bVRv1U?PxD6+Pb`@zc520wgJJv&eMJCvJsTa)5%!A-9izProQqTU@0YciKyNZhKk!v&U zo~h~SY=hKc$z9kpiu;VOuia464|6R2v*~@})KlmZ&C!JV6EW$|KM1NRB|gs5*wEvo z!g1a3AATdEkK<|&SA{FxdVAY&X?2q`1jj3{8#KI#fkvi3w{sDJ`}neMECYHR-K4d( zN;N&<~CuHfLxC1E!+|LKasgSg& z__4S{(6a*#gLxv1p#jSIitrR_RZd>MZFvNGs~J;1CKJgj5cgp7ORsjR>XQY<6~?sK zZB>G@yF)wwpEQ#M`Tt4bu#5#K%fqA2?utTP^g(^GJ=pW2kosPeBD5Vkv1is??4Rn@nLX4sFCjC;#BexUN(`V5sPo7f9 zYrVXR7GBAhX?!D+WW1ADs>qn^c%S@lNGsYqItD{Os-~u*;Mhyq+Xx_d)h5sH90Uag z30EHMn3Z(;_B>b9C)+MO_s;6$job3mbLKZaz>c~1fUsSX--U~*f9ea(Y;E&QhF3Fl^Yd4*rpix=Wh4Irm4X!8EMwR;PXDQ>oS1-#)9D5yV`I(@ z9pw+NCdawVrE5a@_f`LCL^Pcr^lkJ4fnDV!Ru=px(*4H``8Wg5a%*vn7S3UehETOM z(SIhG4xQ9AHHS1W^utXO^eV1H@7lqqk`i@`gXf^lJ)8>9zDUG>>v$uwz5A_=K}Jqu z?RXapY1Oxct?#i-^JW^7xpO<7qwJ8r7gw{_!cR&Qn31epUS-*5j%V zGG#AINUV}go_yga(I`@48z1qc;}%)q-^eRA$dC40(oPd3VDz2dfHhw0kuphLUUc5o z8`|1r>c7IS(Nle~@43x$ozD`|FP6{s50L{p{j_1#>3+%@-{T^C;j}^&ZP+zWv67(o zzoLznFf&E7(MBSVZ#b_$J4AXhRn^r8RxXI*y$bFZLxqdF(4f8=!q3ldBiWSA5zj8t zl*D$$o9&&%>Pv~cw)|Jy_nwF__BjBo90Wn@oD42sjtTE{(;-3q8WOlRgS}Oq=W43^ zJqguuCW7(m4gOPxSDpzLT%bOeH2b@EcsOfuv`S3WVWGcGB~78?vTq|oPExbXf(59okMilvmzz)2E$DDiEh-rVj}s9B2q>X))CLc{nxN|@&?A>^=*#2MY z$wC{iATKQugSR8_|A6ZfS?iPlAIlxR>B3_#wbQvD>+0sT1F#Q6u1Db6wi7(#mHY?Z zK*0S3I_nh8%rZ05HB~!*iBm>=>HSs1iIZni9o0lj-^giurjsCTaqz|%HO2#fqJ->C ze?ps|>#g%dufC64BdbfAfkqsbB_}_XmynQ9jn286Q$n#)JRSD!fnM|T6oSmp(|ml2 zx6{~YLug}&2z}>yD!0Znzt9uH!(Pd_k&#O0o%Pu&OJn0VGh8Jm>xr~MMvqm&aaK)w zkVhn2%g)K{Pvzj?U<$akGuiY`Ob9YqJWape!rCZFPTG)L$mA?N>#kDHmsF zt#{}x1uWT&{KuJ}3G+q`@OypMIQ_(<&NYXQxq9upuqD!O+gM_YQ1Gt43%o^wid(xh zKQBGKDcdieR>*8F@isMenj0&d2dK<}f3Y46hmHr!oltLpI5TH7?Cm$+ zLHiV`Bh8fxdLix14B5vdj9)}4crK~2%USqO->%r67SLK=Sa`MApL@jxvQ;BAArCaa zSjizvi)UtKxgS6un|e>*WQ~a`hMlwCe3~avi%4O>0@&Wouh?(bv6db!Y3JxjM?BO3 zUU`WFbTmeqL_dx=sDn9BXLMCmaPv2xw>o7fC5dxe_1o9yMVDQ*F=y4e$9af-2SJ~l+qjYMaN|5h3p=J!{5sYfcQW?e{4a<8?+BrCQ2!Y+xU4+-4J-51k zw%wwF*;)2m)X(=V-qMxaqciE_-6JF1kVIyrg=BLB(ux*zmLBh)Dcws_GL)38Km3%D z(It_jq}(>J%ElQ;Pa>C`uog90Z$T>7NF)#MIMZ)L-XxQSyvD#0mj{LTsJml<_b=D9 zaF;D$LE)95Q{GX-s41O8hm$&BG<|Z_jWMTg`p4tweCT>!M%o`7L+J#T_V^Zs;bA3S zJ8jFVrP6tiJ$RX#B!p-7={3H%h@u|mw+6(0Le~`uRI=Ki{qYsL+;i*JtvP7**tLR` zEa}wJ7-D?};00Z^Vu#!9hqLe^DUv9m@h0?@61w_S0>BYL{eR z98_|3II5eLGV?IQt8tX_hQMQqN_doOZ!R3o{{pR;P=bZc8=bU?HF;+z^-wB@!?D8Vs%cI8D zP~pi(NejhNh`#O)GQWyXc3K|kdny$>^AV09Y<+%&z>B3F$!?;ehbI(O2j1P8idS%+ z^5aV-Cvnx+e5G+&I+OvC(xAEXc3M8wP6Nk#=YIeG>I7@cjg>chMk8Q#vBz|;l;#*) z!lx3M%9{7L`3Fnv1_p+QZ}Fs-Ow;1&GM@NQR_IWQd5j>TI%~eI|G4w*m?AhLRnr&B ze;SD?lFsQ^1291skn#3QKfp_$@385j#>@VKU|@|deEiW+PRltybvr#*&o5Nt&^}N}xvzX*tjs?txX0O~t8LoV^Evq^(&N z_fwq9(uGqckNAEc^FH&9x^7x$-X9=ANO0@84zBq&u3e%fPCE^2+G{F=3$U_7Q<14| zLIxvUiKX?Za#sX}s9|D_@@)!9lu-S3i6FE)sq&b{FVX6(K0c+J1wyGwo-t5lA03VJG2T2R^^Tu`>sKT6p zc(@2FrHr&fkqiqf*Arh|`uW$2C?ef!+VBk^awQPR#?+MU^h0PkZtg61@&otvS8 zEU~LBDlI9wGY-*9u3!0$@~^8j-+ZNa|NhQ>H~_~g*Y;TR@bE@W5o4Q#&I(rJdam^O zlSSNH-^I%~WK3A_bd(glsd)c%QNR2Ci%W$>)$r|q4`>26Eid&R7Tu&K(=ww+!#!#Q zZH4q8DLwtV2R=+E^v=w&9Du@g0>2ks@Qu977 zn%{?7sZUJC_{rS#Os9AIQxVxG^luczhtV&Tiw|KPJTYgHL1{(bg+n{u7fmJUG`R!Q z(JY`DytFzmr~#(pNgysGSpPf!sfvoK>ZgAllddV>^Ru!$U&EKgeeH&{aPE+6fXKlQ zN{@`=*qiHjMy~W3E&>DQ`S0wv;%4$&w@*`bTj{j7WB#W;;>WSb&GsWS-?=hXSzJ1Y z6ejk}e9u2>oc|z>^1LuVetNZ?x+hp)`V?F~9` zK$?z5p~wxeUvB`zXVb2C@mogqA_AAW(1rQOjPqN-S{)x&vgWkSxd6f`EY6E5FOgTV zP1U`VV*<+heb>*a(g&D55P_wmd(`n-hPGY%6w*d%2mI7>rFKUloKmex!>zjlA$p_v z?8fr1*5C9>da%ALaez-U4mPgOuhcr zHp>ES|327XftUSrI~A3)go4zOCXOpdCC|yKOBaE)3a#2d@6gxZX$Vuw)Qz00iJCQ@ zu@h9GKB_1f(Y1hqCtLqkMxNaO5*{6A;wml2cp9B@xZx$`8#(V5JW?ANaV$inOpB%k zm#Cx}k|aWWPLL$v(*$2frP21soyp+#M=a5Z3k<#9HxP?2JG(Q>i)sF^3S}2zWah!ouVnJ>3n;jXhs7I^ z!Nt7MHs9b{`#+M_yC7y(s_dzfCP9d|dSXzGP_oIwf?(Oe7f9?4;o1`~=Nn-tiLO%| z!o9O5NW2`ZL(L~p^;)KmFIsX!@>r|Yk@gcYh0SNzTf`hB_<4z2=?1-+cu$9#e^6RX6Cv z)Zb1T_xlwDSl0BQB408`SFoAj%f;?}J3ISs63J{5iDNZwgP2tF^$ofjK?>b{>}7>O zJmFQknLxd&v)z{r(gJRMv=DV;?g;H1Z$h~$TQ~jUcKXoZV278N7lp$OjRt0$#GyHT zcI{BqpIbf}zdbE~`{PyY&@WQ+q!pWsOG->={N9NsSjHW{W^b<{!SkKQYrpyeP*;T} z4P!=&u(;AvxxtM#AA*r}ns@^q6Vik^Bdac|P#lsoLhHIl z$lYJXDFL|~n9)0PwkJ)MR^Ss^9_gn6>|1Tt1_{QV4gqnuQVhx>ehdX1k@%K&1D)_AXv0LOn)v<+*+~0bNgmwMMLFG4@*V@3jDTRh&{ktH8 zsZA4dqq4`fzlGo&V(1{!ZF}z0ZB=$NbW{baQMXONlNR^hh4&J5!ZE$V$|Fa5Q{w)d zwg`No=AVC)0SDqiML(+skPYa8DqXooS9n%6dnqnFa6jV)Y$s)h3+6)Km*qW7)=wZO zw0bgQ1yb|-hemqP`FILv6!A1J#B^Cfi%@5}oU93dfA$G|@yF*Xa*-6YYp8o`$hiNBf$9BvFyNUb;rvp^QX_YBP{49#u+3k?S|TK+u}|I(fG ztrd@d#cCSVPA!o!jxML1jw14_8muZyQ*0R}RnsJFNJUK_eXD~tOKDO?ZcPV!)Cpw( zJ-#!`{BT_6k7s9Rowj;>BUWai;u+>X8h7I97yBNoircd;s_ea{=J{++@f?{Lx1q~s zQZo7hA3JaRSqAo7G2x)Y#$538-<}IY><+bllN%jLLI+7(OR8PmgG9d*0cm$0}fKSTB$t6fcR!m#c{$$5ZF)f2$uIFqVCAF=97( z;uRt#3=|}P@PTLT2o)9)`Im62+ZdtOYF zDr#$s#(>THFb@3Z-5;<2E=WQ?2PXR%(~(ok7bd>nfrw{v+tbso0Td#*A)lCirEW;# z5&EoB0e)}UjDqIQ7GvG^L)8;l+AOy@GA%!uRujEi$J`0X<)P1c(iWGOi96>*SAT!+ z0=$M>rO6el_Nq-K%zf_aGV9K*0iNkhOf+&OCuXzS|Bg=|* zYPJe#=^|P|RI2cJl=Rwr>nw^2CE!-TiF{Pfsw}@j=Z-rnCqYgw!zt8r%e!V=C{h zCT3{wWzKA}%A;~Nv>sH;7XZ;#IC3MJs@M=j_U1}9i=ZBX-w_$2%qGu0Y`i%Iqak-yuo;V=%tN64 zHSi5sQla98v|!Thjt`wQ_0d8YTKhHZIZ%`XV77MUFgve8-H(WPxM(&X;HDi|0;B&O zPa%BsW!?{vMgw-CBtQ zg}LW$k|vpcyRR(@khHORN+ zj2LE+luwgvXrUD9#b`S8dOy(kS+9rukj7cW!XmSm*{wGu<2rh&`nb9*dEA3V~DKE=w<@{v@?1Z9uMM<3M& zh~~nSb8^Cg9_)L5z?nS%nFaHw52US%*mKnUe|B0FTuBrF->Vfih&2&VaA`qA>HZjQ zQwNmR83|R)yUNPSS%^m$yuG~>p`<)V!|vm7jCqOts<9AN+^$H$?-L!&Tm$rPS%PZD zyAhm4FGaXax##Iq&}sp9n@P7%q%LzrNvn#V$R=CoV8-WVkk;+%sGZyQq=g71Z@1vw zo4dybc`Hli>J|36Ki6uzwlPc~By&*bZ+uQ&2CCs$mV#@WB0Q-^P-n8RTUS?CTt-HQ z5I-}+6-N4~K2!-E-I9c0(pBibLHPH9vGa3z=D%IwWjex(Glu$W4#>2z=IvR+R}k}9 zgLZbUA}-`dOH^ZISz;vJg?%S}Hpa;ACd_{CS6gKI)$+U6*8l z{l7PM4#OvFjcAT62VC-VVf*c*7VZ3)Agk_{MVFEEj+$bkkbL}zs2}V zB00|tUtJV9dT(QOHHICIEAA9O|1lDY6nOYys~^lNDZ^08l{H)${kNTsN@$MM$cSz4 zzdI$W$3+r;p2ZqZ(iGe3U+}vqPZDcrNsUjwWd7NTp=kGqxEjmAt_X=kgYeaz=heJ^ z25D~0yG`U$v0Tn#WvDaxds$fN@$v*6X6OgU9h?!<&)6IjI^&v9tRffasJdTm!h6Cj zvEwAT+u&|QBy}aRXEQXncW6hWW8t4eOsh>ze?LiZFy8{0&{nJbZh3k6L%4SH#Ke8i z-LC9j7_QC8*rAVlP*zgX?K=-xRSATam;CuQ5IZCHuMJeS_bQh1J7?7P?w;ycYckH= zdiQ5;>)C5q{uygAh!fC-~D_L7JJWuz$KY-T4Tw_kHj$o3IKm4z#k7ScvdhPPP z#-ytSeWF+%>cGH2L0r457Uz=>2|d4M2y^v{W|D$O;}UlgWL14pM1RTKUG2XwWLnqn&<{D+6-^*xb5~R?rV$fYl~+dVEM|t!xH_++YjGsm{Dh> z$Ibb~j;b??sR|S27(!SKp_q6zhu?T~5Jyqdru)T%i+?)qr-l1L5jPnL&lv#3YAUtv zsC}iuD|Kuvi0UkZ%{aP?rPrP88jQOsKT)H@EmmVNA7eVaWQka|L4I z9~`^Bb@usWXq!lP@DU3iY?g^1X2UK}c6x{GWTQ2-s7;uWcsQX5Vgj#b57m<9YWKe8 zn*1_sGO;m-IOo&$Y$yc5P)OerSGPN(qQ;Vu_tBF#*UE5N;}g;QRserCIYye~P1t#_ ziJiZ@^KinIu2bmw_RH)q_-A3cS$NFY>@z=R5j^r9o@0tDl^iQWFqfd>!eo8Et5S|@ z-{C%rGeVr7Bzivx*s;|hAd#Q&p=Q~Fm7(&D+S4%;26PWn_QQtX?bypF)v*`_ai44` zr@YH|=W$03Ey$P!{Ud#IG$XSo!u~WNYl9~w`k2=vVsYqC6-^c%ZjqYS&fA4V8HKtx z8X?7Mg`&E3A(|xT!*o*Ye^{>~lNyc~2BF0o9*D{6U#)M$jLop{9O2LYZ{SbkCBdnO zc9U~v!5H6*=$9CltN8ySe43j5YJKh%hjuSEzO7$aS0vr8Yo$ukapk&e-|`R&bFq6<78TY8RTc{kk?-nS;N+;AAA4lpPH4WwzY)f#Y3pnw<#A#^p{>6pL z@xLRD$Cd*LJLLB@PVRFXm;)iYmcEKLVKq`KQI5BGamC&O$|CEbaqBd)kCXnh3|3BI zmM~ozOJc0#q)1ipcuZV5WmRio-74_Ux?)WY+S%=67gewsM`ynIK5Qvy$3azSX&jY_ zDV8c;ynA;{`bTFX4{GHpnMriT8#h&Tzt{Z7<{2jWNklEPgcE(*Y?HfluTO4?pp}-K z^Tu*!)=o~7+{jEJ+d&eYxk<&IUagK^XU`N|jIfK`K)zpk;LjgHejhuot=@PHxlzEp zku7+4rFLlMAG&3;`iGcVMO9aXS_~_i_nB`4i@cLT zYF1fg!@n94xE%}kO%A4uJ`W#car~U_H+@~v(A!lw5HaSMA!Ie4Ubp4Avc1zum8YW! zl?k@n!9HAPt3Kls zne4l9fvA0q&b-#rBGBm9SIx=u%EgPuLxox4Z|@6b3myvn+seK-DK1dUB2mn2Pt;yq zd%I7juFokY{{LVCejN&oJcO!mOYvhiMJ7qERQq6kA3b2`-Fy&(GFG{yKE8e`0297| zB73a8a9V0$q5DYC5t?nZw!+kv>Ey|vdn+4yd)yw}4wEvF5L0UYWEv=WT3G72a3Bgp z_i~eB^-FrheDcO(3Te-$N#glTaJYcsDWU9SQ=*E#-+kxvy=LgUv8X+tzZY5QK8m7+ zwypZ4+7{J|U$2Uth$m|wHk8&*5aubwUr+hN#9q7~{&V95UA!6oP*lc{u}8rZRHJsX zf7ls=CL + +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