be>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-qoNnRABAZiFaA$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_kO0o%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!{5s>YfcQW?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