From 5d3f00a5372906caf407d6b61c34914d24213420 Mon Sep 17 00:00:00 2001 From: Fabien Basmaison Date: Mon, 5 Apr 2021 12:46:34 +0200 Subject: [PATCH 01/14] [l10n] Add and update i18n strings for French: - Fix typo in English. - Add translations for missing strings. - Update translations for fuzzy strings. --- .../templates/snippets/table-sort-header.html | 2 +- locale/de_DE/LC_MESSAGES/django.po | 2 +- locale/en_US/LC_MESSAGES/django.po | 2 +- locale/es/LC_MESSAGES/django.po | 2 +- locale/fr_FR/LC_MESSAGES/django.mo | Bin 24395 -> 42813 bytes locale/fr_FR/LC_MESSAGES/django.po | 738 ++++++++---------- locale/zh_CN/LC_MESSAGES/django.mo | Bin 38419 -> 38418 bytes locale/zh_CN/LC_MESSAGES/django.po | 2 +- 8 files changed, 342 insertions(+), 406 deletions(-) diff --git a/bookwyrm/templates/snippets/table-sort-header.html b/bookwyrm/templates/snippets/table-sort-header.html index f016b4e27..2f20b9291 100644 --- a/bookwyrm/templates/snippets/table-sort-header.html +++ b/bookwyrm/templates/snippets/table-sort-header.html @@ -3,7 +3,7 @@ {{ text }} {% if sort == field %} - {% trans "Sorted asccending" %} + {% trans "Sorted ascending" %} {% elif sort == "-"|add:field %} diff --git a/locale/de_DE/LC_MESSAGES/django.po b/locale/de_DE/LC_MESSAGES/django.po index 87e77637a..962b97369 100644 --- a/locale/de_DE/LC_MESSAGES/django.po +++ b/locale/de_DE/LC_MESSAGES/django.po @@ -2565,7 +2565,7 @@ msgstr "Zu dieser Edition wechseln" #: bookwyrm/templates/snippets/table-sort-header.html:6 #, fuzzy #| msgid "Started reading" -msgid "Sorted asccending" +msgid "Sorted ascending" msgstr "Zu lesen angefangen" #: bookwyrm/templates/snippets/table-sort-header.html:10 diff --git a/locale/en_US/LC_MESSAGES/django.po b/locale/en_US/LC_MESSAGES/django.po index ab28a3942..a8720f17c 100644 --- a/locale/en_US/LC_MESSAGES/django.po +++ b/locale/en_US/LC_MESSAGES/django.po @@ -2408,7 +2408,7 @@ msgid "Switch to this edition" msgstr "" #: bookwyrm/templates/snippets/table-sort-header.html:6 -msgid "Sorted asccending" +msgid "Sorted ascending" msgstr "" #: bookwyrm/templates/snippets/table-sort-header.html:10 diff --git a/locale/es/LC_MESSAGES/django.po b/locale/es/LC_MESSAGES/django.po index c0fef6509..64921d5e5 100644 --- a/locale/es/LC_MESSAGES/django.po +++ b/locale/es/LC_MESSAGES/django.po @@ -2557,7 +2557,7 @@ msgstr "Cambiar a esta edición" #: bookwyrm/templates/snippets/table-sort-header.html:6 #, fuzzy #| msgid "Started reading" -msgid "Sorted asccending" +msgid "Sorted ascending" msgstr "Lectura se empezó" #: bookwyrm/templates/snippets/table-sort-header.html:10 diff --git a/locale/fr_FR/LC_MESSAGES/django.mo b/locale/fr_FR/LC_MESSAGES/django.mo index bae2cccfacb0d3e15078b395dd3b49b0c75cd72b..09e90f22c801bc681e5dfda3c171b81a4da45afc 100644 GIT binary patch literal 42813 zcmca7#4?qEfq}t*v$4DZYs7}yyY7`{Wr|3mp~<`8v!<`8wF<_rwX z3=9l%<_rux3=9lPP;q^81_n+B1_ldrNO(Fy`9V-R#vBqqY32}nO3fJ3NH}m?K>Vj<0kOx-0^&|r3rKu;T0p`% z+k%0Cmw|y{o(04mYb+rC+hW1MAOs30sQPnI`43R~hXn%z3n=MXGB8LmFfg!MLi{Ob z39(nt5+d(p3GtV^CBz@WmJs_QEg|kohpH>HWMB{grAtc&hDZhmh8{}>hBgKUhG$T9 zxmFPUHCB-LYO;c)<0)23))4>8 zT0`Ph)fy6ide#tkT0rI9ts(aNL+M0oNct+ahJ@E#Ylu5HSVP>i%^Kp4z1EO$IBgAa z?{%pDCs6TkP;n+3Nci#FFfdp#Ffb_FFfiybFfhc~Ffhn7FfjDmK-{_32I7vbHVh10 z3=9m%Z6M}eg7WX%K|Q2wxkgln`NL|v*K#67uo z5O>r<`Q3Jq@?y3f#GM=MAnw=;)qe^~UxCv1pz<&5AmQ;9%4e{Ln9pku5tp@RV31;9 zV9>CKxX0NZ5??j;kZ@|Uhopyz_7L|ig3_y?^d_jfUG|XhIcX0`FZb*r{(5c?3BQl_ zko3Up0EtgA2S|R%w9vht?^~ymfh<|QF=_gS6w@#37{0lWl z*csvWE zs{ge!14A?e1H*4;NH~SNK-`_^0x>_=g@M5jRNlKl^7R=Phm*fhuuh10|zO}9p|Fl5W^+4q(LHYBb<}7h#U@&K3 zVA$yj3HOh#ko^4*YOa(U#2uP$5dT_0X-79mdiQdJxF^;P(oQLIgP79~rRTUo!egZy z#2=g8AmO*u4dTz^ZV>;Uftq&@YTrjU1_n7$edEr+pv%C(Any*b-`5=?9_9|wSLzNi zza7e-2&EUhL&9ULJH&kl-68FwYwi&LF?vAEk@bM+xAB04mzxK~eSRJgf5bq=Gok7V zJs|F=_JEYrEgn#Rc|iQL(*sgY?(=};=j%}QFFYXe@)2sz52!s%o)CWsctYY=$rGZ^ z%oAdcttZ6XFi(hkqM>w#C&XW+o)GtRc|zi8vL__HE%IbwP-S3X*yIWE*9|EB0IL24 z)ZUMt5dSlKLG0!Ag2bzc7o>fr?ga@iA1{cx8D5ZbqQVQ}?`EjJPA`Z%CU`;IJJXAS zVG^jF0hKrOhPcDR8{$p}Z-{yR-VpWi-Vpz#ctgU!5X!HIs_%iSpA8jX?G4HQJD}<> zLFotH5P!V$hSW!2y&>Tw;{$QGp%28}Ha-w_PCg6_peD7a4@6zA4+Dcf0|P^o4ko4K^ z3vtI}Uj~Lg1_p*%z6=aHp!T&NM17baBz)rhAn7C752C)@4`NT9A0!?3`a#^g#t#xM z`=I#UEmCusyd!o>2SU=@v_Odamjy!1 zKL%BI1uB0h5E8y`pyHncA>sKu5aLhKAc(wN5X4`qK@f5MAV@l}2!hz_7X&ds45}_A z2x5O;5F}jcp!&LlAno5NQ1$PEAnyGW1Z~#`L&9Gv7-ElJFr*wa4~CSBNx=*ZUJMKj z6QK0%U`V;77Q(>b!oa`~8Um?jXN5r0_ofg?_#O{|l-IXH7#N&D{T`^iRVXCA_=iIB zNmMArzX_obf2D^)+>sv&@lQo4Bpf(ZCe<`-DjZk55gej$0w-0lHm~ltA#_-gI+j9pGP>veR1Iscjbgb!lf*n zfgyl_fuSuN(m%Z(4(U(xMldkMF)%PhML_yvJ0l?J@l6B+xPQwN2{E@l5@OEWNQghy zMncTH90`eUl_-dN^P?CTtU>+mC`kD1kAlSCnJ5N^A_fMA>rs$)iB~j4d|5Qa{Eg9& zbaXr#5^kTOA^!Uj&A{Lbs&```?G?Wm2!C!2#J#U$7#Ns9{oh!KzZGL4_Ugw%+-C!& zdt(_G{6YQxSV+HLFAfr4$#D?%IdKdO8Vn2!m2r^ty(kXSuU{7j8E1G8rCs77{r{Qq zkoNoAcu4wHOMuvKnE+`|yC*>Wk&ytgCociwpK_>peF7w1v_knkP<4|MAmKAR0g?~D zConKXF)%O$CPL^ni4gPHlOXy9k|6Ofp2WbA02+Tug4i=D38HRR5(9%bBLlZ$OhE|sgQ92wlqjOut8!dko0jsje#Ku)Gtqml!p=NkaD&q9b)ftsQA8gi2bk9A@)jUKl{e9f69Tx`>z~G{sUD~>W>vd?7LnFamT$vi2FVjLc&?12x6ah5yTzFMG$q~MG*hQ7eVwFLe*6i zLCkF^g49zJiy+~>tOydWo1pwdMG*7P6+y!1VG$%hye@+DQ-z8l{TYK|NV?7_hJ;UV zF~q(Z#Ss6`FNWB+wHRXGF)05wlzsyh|62@kw@?WKgAD@%gIWoseVAOrz~Iinz%aQ4 z;*Q%T5c8gvK>Yiy1d^`!N+Ib(wG`rRlTwI%?ojcVQiyx9OCk0&LdB<+Ld;!K3JKSB zrI7r3r4(ZR2dH|EGDv*OltJ>lbr~cb$CW|Ct-1`7&brGW`EN=Y#GNO~7#J)W7#MDr zLBd(M91>sheeNq;9QAm*N{fP~M33W)o@RY2nNUj-ywIV&OI zCQ%6qC!I=&e}XC@;TH#$&!~j7t7|GD_O7gixaUA6q&37p>%W&#DD2E3=A0z3=9QO^*3uE;r_4&l5f64)%~l1n9p7dsRyNNA?`A*h4|AQ zN+;Ap;-j(_V*dPEh&xu)Lfo;T7838rYa!|3aV;bq8R{V75_OPr%cKtC-Vi9AP{+XF z&A`BrQwOOZcRs)K|dS3M+LMCu{+iF!T6T&H>lh9pqCs~!^Gd+Q z7y?1#H}#Nm!?ytvo<$9iaAp8E}u`t5rIBpwwS zA>pFd2uZK@jgb1xuMv{3q8lOZ&TfR*SK0^(ueL^ref^D)cF5XBh`%_RAn8-O3F0od zCP;Y3G(p0vxCxTZCPURNY=VTtnI=elJa2;7!`lq;zg#oKT%%@4{J1tl-0Ry6aYq`I zpWh78-vQOPt{IZP4mCsC4ey&F=|H0e(!Q{2fta7!0*Sxc7D#w?wm{sov;`6#2U;Nh zINt&p|9;v6N%sn^kp7KRD@1<*l&)!o_^+cC;*V*q5O*(!((9q>wzo2X`!$DKA^u=% zgQ(+ggVdkmZIJxz+y)8%2q>M@1~E6M4N_m%wn5xCuMHB;TiPJ;db$nbpNmj)-?l;g z^|cM+et~vK{xE8X#J5j7BwQogA?dxS9pb!v~T!3Aodz}K+>OI2gJU#4v4>tq5K*szq144{z)AS3|$Ni3^O_)=~=lG5)K-j z5ceB(Lc%Ac6Vl$#?1Y4SPA4Rut2!a>>*<7q%bZS#yEb=1;`K-;BwbvFn#U66Up>s=6caCAfbE7c9ruig!b zH;Zma`i$&`gi9M#-8?9NHIzQw4XGDzcSGF8-vhB%vIpXRB`80-hk;=!0|P@!52W7Y z>V??r(hG^Nz+Omti0y^wuk3}`+tdqj@2p;kdr$X5%A32rkbL(CO7r(Y(u-0bBs^{U zAm#@5LDETlA0(c8`yl1liatoX+}sBV?~{ED45bVV3^)29<05|jknkw$hnUme4++1? z{g85EO+O@l&-6p`#lwDxyI=N0!tGN(q&$?L015xX36S`kI{^|-YbQYBZ99}cJprQs z{sc(4yqEwn=j#MW`&nrs#D5VJq3M4jM1B86NI1-x2=Vvoi4gk^O@z4b-9(6e%#$Gg z<(&k{FUpf3;h;4M65pm!@vun{|HMv$_&;?LM1R>Nh#h&@}ULh|8_sgVBZuc?su3!4U^^QS@b zamO@>|N5pu{5@kD#2pK!LBe_UG)TOim5y>gnhtUAjOh^lo2Nt4!J+Apc)11@zYA6WW;!I@|C$bQAI}VkeIhd; z^_ap8i22?#ApVG+0a2ei0}`&~Ga%vI1Ld!n0kL=Q3`qTd2CDxbR6pNLi2o#JLiAhB zgrqP3nGo}nWA7JpB>tz*g`}sub0OjUYA&Qd z^>r>}oS<(WL|kA#Bz&dkL&jUo=0n0Yb3P=$6wHV8w=3sE+W;GJd1D1fo7^2_&4$mO#p*mL-t%x@HL^95yY1*tZ|b zKd}UoKCVLPyGtPMe!7H#A(?@J;Ukm|T?*mvTng!z$}WTChni&&^QJC?)GPCrF)&0h zFfg2028nmY<&boxy_|u8kpU7npkc1(j0_B_Opvk#G!FpkSfx(oK zfq|8Yfq{pSf#DJ(1A{9gBrh*zWMB|tg2XRq4b2g#9y`$Z7-*1;0g`q*85tNPL6(Bn z<1jKX{9#~VIL*kwz`_I>!vSf_XJlZw$jHDT!U(BvL1LhJIUlILa?qL`(6|c&q&$dc zU|`tG$iOg>k%57ciGkrHBLisan_&xR3lBpOuM$VLg<68Z@>98joRMVA#RP zz@Q0LyOEKB!JmHnu#>@bTtE&tQ3{H#;3`!t> zFflMp0gc0e<~$i7X;p`bfuR(tPY%@A0~rLxFQ7DtItn5fAmefJObiUkj0_Czj0_A< z85kH?7#SG$F)%QQL&F!;2UlT$wCzFbd7xSu6qy(pHb6N)p?1bILgp|)!jBjk7(^Kv z7{r(u7-oXn{fvLE`fq@HZH^>}qMh1pcp!FJz3=Drk z{U-(n1_34ph9iuS_9tkK7DydvtS}2|MiCw`dRv3QvvWdlgA0kk#*#9&}xxWmA}@RNaop@WfuL4lEhVJ0I3gE1om!+cO$ zfU4QW$iOfeYCdRQupi2XN;7O=WMKHnz`(!^73YEa@hKw%s7nS`md?Pya2L#CV0g&@ zDQ7`rn;@Z`j0_ACP(7f2{0v40hJH{Q1xbR&B|&Zjm7h@a1)(%(?bbmC28PWH3=AI_ z7#OxOGBBtyGBBt!F)*9~&Fe5SFx-cl0it3-WdS1tgD@il!)MTV8AuR{qZt_(Y@p_X z7tb*;Fr0<*K}s%x)`fu>3=9k*Q1N;O$hdVnBLl+$1_p*6&|C^=9S$P{11}>3!wRT+ z(3-kaj0_C2P<@~^P!)^}46_*+7`mZqjzQ%>ZlU1}lKRWYz#tFR*A5bZVl$}N zV^F!mz`#($$iUzU6$^#Zd!Y0a1_lOBM#y+SXpL7JBLjm4R89vpmJMb9U}Ruu0p$fy z_%JarOatj-VqjRp0BO_w2dz5nnMF728MqO3=A(B85j;SGB6lINKb}@cOugpgMtpfuSC%W;K+)&A`B*!o*D1nLvLg{KK4I0A-se1?IgW5D8z6v7)!&j(SAE^8W2{JG+EMjC}*v|;* z3xI@77#SF(nIL_eAVvm;JVpkF0!9XgY(~hu$03j+DCPsDBL+x2;XG)KC{+AAl#YPf z2jb2Hl^>w8m63sg9U2zQObiVFLFR(ig)%TOtc0ou&37&Yl}n5a4C|m`pt&Z{I*~h! z3=Ho;=>cRY8s=wWU@&K7U?^r}V0aC(hk=1%K8TH^?kdzgkWe!t1H(}$|2`uF!xKhG zTPY1H23r3)4ax?Mok=kz@P^TQwGSqUJq0qXss^D>}4PViZ4KE zQ2TKSBLjmj69a<=69dB>1_p))3=9m9LGwsZ{oK&70j;I_!N9<904feLLW7Zk;V>vq zFfuUQU|?W~Wn^He0ZBlyA0q?9Jy1E!#K52nN=u;f9<-JalwTMb7&b95FnB@D^JQdU z;AdoD=mM3+3=9mpP;p@<1_o)6IMmE5pf$%#3=Ex6K4?wndr;j8<*P#Vf!0*)Vt~w5 zR)g{-BLjmqBc#u21XXj55z=;^!pOjoz{tSx78G`j3=D;gkhbj~sJd04uw`OkNP_ZD zFhbgRAS*$0ZlL)o2T(hKk%8eP0|Uc1kOUOJWn^Hu0BU0~GB9*AGBC6BwzosogTosofIBLf3NBO?Pt10w^&Jdg%ZK4N5G z2!^U(4k|+#85p)RFfhDgWMKFO6}N-ZAT6NvB%n2<9-uN8Bml)z85tOyL3$Y(7%~|d z7QFb_Wn^G@2r9>z7#JKuX_JwGA%GFmuK}&m1}VSI$iQ$G!~oR|P;#1=)B#xKi%}Io!c`$7u{ck?M4>pfL?Kb3C^az!6sqa@i8%^s z`9%t<8kMPuMViHW444ipNG(cE%`4F?R#jC<&d)8#Ni9iLWKh*8N-W9DOV=z`C@x6^ z1q2Eo6b8i^iAAX?@t~vvazBVx$j<}0u1KLcBQ>WC9H>}S6=P9WTAW&h@QOlSJ~RkY zi$GSP$%6c+fRx@42^Az04~f=dh5R%HRSi%Wz(qiA1SeonKp+{K2@X6+41fYIF|QJo zDA4RyFNO!Cf>VBeHmb|iixt4eGpK5mCFYc-Y8ESG7AxfCgCe9XF()%cp(r&sza%w2 zGX=L2n41`2F{Tg%jZ63Z#2ie~fTH~LqSWGI1`Sn>qSUg?)N;6AAzV$IGR2w<8XlP? z3Pq_o`H3l@%v@TKl30?esL6meXpyqNLQ4$3 zlUS0P0xuQt4_|(%}vcK0i`2Q!G&Hxfyy%dL_6x) zS6G@~lB$qal%ESOsc3E=B`KiP?C{Y0_K8zrI45dDuyZ*QY$ixONv28fwDDBWoEGg)Y<6Df<67L7#zXwR@ErZ zEJ=+A8K+qc6$6KJacWUnY7v8Da&l@x36x4r0Wm;z8G~aoC%mEc;a7Hnh<&s*QT$EV=F)JknTCh2$q$nikgRM^im1c=43ZMc5 zqNOA;9jvt^AKV@Qu~Sn(1qD({00je5LI80gJ_N-9L&tb)EBz3i3g%1qR2wN)XP^OI1it%mbw_NC<$^CO8&AZ3$!*(Dn>O zm5xGjX;M~datTN|I1win6cpu`CFU?V78K-EDx_uRlz{3rN3d`z10*(}l!9kUYFc|mF(xE%oVJX`{j@{8eoNUDNFoKt4L z6@yb|QAq|kcC8qka`HiDg2>bqh0tP`lS)fc74nM|ApTa! zEG{lh1-C-ri4$gTdS*#RX_8)Yey)CQera*4Zc%D#S*kv$BrLBi%2l#+&n)pMO#-<> z5A1EQ2SF*h7|ex|poFig0ZH5tafOn^^z_se1tnFDlEid9h?EjYuDAr$#40H*W^hi- zOHR#UaL!1~OHWk@fEIlW&N-=xMetMu;T5MQ7A0peIOpUSr$WdS2Iu^gR8U%R&d*CJ zO)i1+lOcR724_gM38NtbrFo!|!8t#-Aip>j?vnhxw9KMh24@HjZtf^}=B4GsMPQ{P z$dJ5}#N-kMa8Ak1%!A~s{31{aOb4|`^Prg!q!+9b6rBo*pd8NN3@%Tci&7IyQW+qW zf+sl3!gxNJ#U(Iya7JoQ8jO>er{I_jZfC$mz}XUN2)I&#Nq}=EgpooP;6)47`#*wBR+@1tiL5bkpY^4BC7|!6%NeY8=X%VPm&8dX7 z_843eOAal^uQxy`y^+0M0gqM~IuD@VhXtoD&QxlU*GRqQ6 zQo#i!gG*{6JbSyOf|~zDi8+~7V3nZuKZ8puh*D5fC`#2$DN0N$fr;ehgUY7lL{QaG z3=@QeHB=9zF#(e*$w(~*mA>%00;&pK+#aDJvsj^^2-2)oC_=26G^J z1I&b_bT9|h76F&!#Snuakp+?hwbeo81!{8^tO_QD6fWiYMTj8*B~=YDALb2k?r;UQ z#u&gPsLlkn$*iEEC$l(%0TL?35W3Qe!7VW}Cl%Ze0rf}FxX^Zo zTVh#ea(s8+Cz`S~TOMGDUOc_pcN zB@Ax)Md|tAR&bF5tiZMhiRLDjSTVR2rRF7PFt~xepHs=;o|*@0{-!GUrIy1AVfWOc z+{8Qv_k2)k4lZ9o2~nY-D6=dvxzdWkJwHDMl){P?vQsOq7(5coQbCPwaNsDQCM}%4dGkE1^tXLjfWM(vb;{T|EXL=;)mlgHK{{ zi2|sbRh9~2gL=c@JZi<@lbTqRr;rOuNbrIa*04l0XjL`9eN%Wx2+V_|yi90Rfr?W- zWaF@?0b5s=3L5SMkJK^vWF{3Q7FCvHfWj1<8+|gf;Vn9!%)D#{P}Kox^JIcbQ4qHn zELaR`l_2Un1|QIn5V)@g$&jT*IaUll`N@en5E79M!GhpcMKOa`Mntr^^%#5;^Agijq1_VS#JtkPoSaGpNRKNOF^CAVwImrFG$0d-8GI9= zTu6t;H?acbiDCxd)RM%M#F9h?U&vrUKBzPWk2V!E_<~{c=WpfVR$L|ZZVrB;+M_~k1=GCH*VTnrb< zEXhnQR;WxZQ3MHrN*2(Vb!lD-8W%kHtE8$?SejZ?sadRqE}aVt1W=X$D+1+aFaxR@ z90MS2pgaV%1e>XO`6a2~c3W9yN-EeHpw4<;I!J2~xP_?z$xC2QK!OKqoE}6Cq<;=# zLt2oj#nA2=#5hpmgtXPbgDv1P8rI5CC{IK-3#7Cdl+2-x>Lf_l6>JYgE-k+_F9nM? zN)+5cG7O+rTX8BV29R63{?0zm3Vx-zNvTCv4E}kk3NDG2VA?l7uOtJ^3QtYVX7JC; zsf1T7i7Akt259gX8so*F)C^80pg?8t&o3xeWbiLY%>xq(nV^bA0hC2j%N5Eq^HTE5 z!4f%;{0*sS{fkmkixiS783I7HkQD>0vuMQ-0PXZcl6gRC5vYlpnV+ZNlA4#9n!*r} zQCXasoS35!P?TSgS_Dc-3;{Wb$*IK*0r>@`If+G}VO7)tsDS+95(W?ftN05 z6sSoCn_q?|cO?Z4RSm4kTodGDP%)BP4(l+JV@xq==HRr{iXk|$EEP;Bs3qnWSSx@= z7c=upQyGFG!*LKwAqdi!fr`So;F1lwGKQ*7%P&$$1P>j( zU#gyyqmTyaRfGD9p!R8I9(*oU2Rss2Qk0mS4XQQ44M6aurb02Gf~26c2C3i$2%y=R;LP+q zh0p?q;LO~D9MBX%ih^@SW?pJ>Dnl@65(C~x3(n3gfb#!!Nhd!SYfP;Rk8Vn%9WiXuaBNn#Ob$w+E0 z#^RFHTstMGJTychJW%|Bbb%WM;N=YI8<)#*c)?E(NQJ2hB2qdeS9{Md_&}wo36yIf;4Mp!F*- z6=0V`mfBP7(HiRah|umuHqFXTaN?Xs|!O&VmfGi z%!(l-BQX!0`xHSlC!mQEaC00Hl%O_zW?5>A9z#e*DtJ)~D6hh%)j(rg`KiTu;1wvT zxga~i;{b{v)kUcaiAAZPB^{6g0W<=nP?VXTQKFESU#_SSQdFq`na%(e3`k{PNxlMT z{s1~Of-nkX4XAtpca9)kAvEKVR&s!Pb?~VOB%_KUt}4#Y1r0=j#_2#=3EVUXSqd6U zhN=d688m4NS>1qRk{i+B0Cn)vpcOQz*#TY)lLj>e*1-pv0%_$iK(b!3LL$-}0h%Ca zfCM50>M(%y!eN z2x>7X2vZnBK+C@%q!mL*K6t8vAv8}H+`9rZ^79c>S)qBlkRdgYOb*gGL}*?LY;6xL z^)Q6yK_ZMHG%qJJH?t%)1?=q!xBv#_>Jm`>0x||#rGu;d%o5OoDsXkE zS*(y)T%4br30f2c@)|g@VHLF=1IR$oFfFJz4GuDe@{CmQS~F0yxd^oQ0%Q$}ClI>w z3rciBjfh0hBxR*SNoH;;sQv}FXyF5Oh?yVg^a*G&9k>CZ2g=59CxGhu{33;9$RsV; z43Nh_)`OZI3ZRk+Yy)_?5Lh0M9iZibaN|?*K}A~zXbc@>3d~q&Iadj8U4cUq6a*0O zffeWF!=|8$5d|wm9y0IU6)e4~|KNq|}`Ja!~RB`5IC} z!MX+DD9izm_<>8`xPgi(aGn4KSCIl_j23Jr)B}(L zz8KV)EiC}0V^G@|ZXY5ULplWcMU@~+AdT$Y#7fW_s-jd-Q$IBiwD7kiUjh5TDnTpZ z=96hQC=nM!Jc*vt89;KNVJL8_0+sckAybebc-01`C`bdix<~?5La8aB#z_%!g=z!E5Iz?YZy=EYNW6i>1tIa~Aq66&C!3nW0I3Eci4Ddn0)-uP z+5s+xvSd34G=T&z(o-40jTM{Xg2X(~41J<*L4Hn-tx|DGQEFnYK4_s(5@`LCfsH;$ ziJd}e9w?)LYP^!tVvW>dO$N{^*v$Mqh#NpPRz`xacFzOr$6T zm+7FY7qoC4Of!H;&>(MOI;eey!UJVQRgIGTlEj>NkVLU&F<3c>gse_wfG(nED9A4^ zDP{l%NeTmKv;|5kWR@^MmpFqeOQg~o)Ru!!1tJe5s)HBgB1b3018~LAObE`{)HWOJ z1Dco&iCLN$4GABp(V$LEY6=6SW61zsw+m{@CPMQjyzc>N*nnGZCHb&r3*h<}G|&$o zmILK0SRKzmg!*Ds4Uka_22ezU`V^ok4AAOOUC$Ir|RvtQ(SVrN9;7uL~MO1$8QPT@p)DtrUz5j0|-ROmq!Q6%386OiZ*53=IspKvUDY zAw`LK#W{&3pv9GLiAkBMc?wR6#kq-@#h?KP(12xPa*0D?QFcbAUVc%!9T#kBTQ?*% zG1p4L7qse8AqceUQNhN?#~}wznUz9XQM_9am#?R6b)FNHiyySe)go2fVMN(!7S3pi_QDTm+TYgb) zv6Vtz0hn8CYhE193)QCW+(7gAjDoLgbfObr4@yxsW}Q|`K84QhgX4Sk-@TuS8AkcqHKTy3xi!- z4AWJNQyV0j6!Jjvp^#XbT$-nla(HE0rUGctytF7)p#Zt%4%!X`E!n|iTOj{Ii*)e5 zs1ndDYTn_Mj~dpaY>hg+vIMl!26?v>R0w1O+SaF>%(9|Xh2qj8g|hr&g~KaL64MW_ zC<2EJHkHNLmBNY<&0?tM3KEMIQb67WMJ6oNkW?iqlU!ZMPKI z!Qfg56h25sfcfLxy`q#}m=aVSCB7X`Bd z1-S^e%?eemLSB9@$ZpUYtCCc>au?jO4=Rzt;|-blpfO|Y zNd(J2s^p@~lFY)=RBU^!U~-^cRza!B8KB|ZDutXxaPopAzTDIjP|BjfhgTMW)`)`&Mo>&b%0v{$fR<9g zi$ZA2BQX=2p7HeokW4}EgOOozW(l|$1H~`>EQLlhT`dK-Wx#ur7((+>k@GJ~#RiLH z1<>xS!z)3tlnRc;lvIUe(BLPi$N>+*|L%fE`>3+n}vW9XijRe0yH4td`Jyb3|a+{S6a*f7l%&47lSru}dt zb{NvSbp-21Ny*SXaNytyN-fDREnp z99{`J6QD=|RP2G3gIh~zAyf)UYoOg>*`UR)Itm$wS0<$vr5|1iZUWdODo}MB9jL?u z<^BBh%;e0}90kx)!&E(mFi^@b0A=7RP$CC!q)ROVMQj?l?gu62qD1h-H8@(6z>D=2 z$}*8gpywpw$#e0^m4@r1TO{AcB$!s0RjO!{tiz5l4T3_5)RymJR+AGG!dZMp+(q>|LUOpr8a&a4dD00Ws?l9&wIYXa`_q7mK!Wv>a z7Hw6~em-Ps53CbZk))P@_Iw{+3DO5nRYL<1aDzd-tv;0gugIFM#gTbx++;DQs} zO)JdKPZD8UYPIg?NOAJl%y);W)`P{>PS!r4tbLzDB?ij1KFquE=EC})WovXD$rc3LP=46 zY1!eGpeA}@DWvJEkercP1d7EfuuZV-iVBcfHqgEV@VdTYn1DiJS!yzJ`yFR`4LZCM zw9Y#plu&|FvrW4GpR+piNECjtYnYsM1g#EWslw1LbQ_YZPH_Dr6J~NlhYn z*Lo_FNGhnAotBw{B=o3ZCZZfcHVs-HAPGSd2B=ntwM-F`D8?M_g!ZpNYEeUj0Ywg} zi~udI3h87($FPeTLcpVPsYP%G5$#5p9&oQBH5uG5fruSm$>0MKP=F2hfyxHZK8D;( z&_Ery!~jJ$c#{BR3p6-Xfc0&qQ?npX;{atks+%kV(GqjXSq zC;>Y)vA8%@As@7J0i3I0n?J!Wa4Sm8JG?wGvzQ^cGPfX+!Ts>cJV>7br7}XQs9jP^ zONx^-7#wpz+Y6D}#~^p4Wu|~mu1L&*#C$0vtAcHE&N;kN0lrlfEaX@Q?)`&@eRE0) z#~ip+bOH@OfDU-c&jS^?It;#v#h|t}xMF~{(U8T84zC1lD@_D-J3z5i3eGsmh>fL= z$;qXu;IbkQv>z2M{X+Ma8h~94-De7CgBih@;DHNB?tvBWkiG_JIsiPxg*NyAs%gQK z4#=%d9f&c|#x`UrSdl_M@EB)=3C zO~s(0-{gEy&k3Ua@Ji?&)-+_RixuF5rVNhYiU`~)O@(Y@O;v#IYGnW&Lji5+f``b# zzJYF>WB}D-(4IZ4+yT2ArQC${{9*FQ6*nl)Ae#r)ixlL@!O!4YTmtHngL>T%6A@J& z$P8$;2T9tn?h3eshnx%quFJ9xF94@(xLF{bpjJQVfCX@BEX`BMFD)wqZE^$;)DFrlV1|AG7KD-iCTV{fW z|3QsG(7HazCR+IX0(dV7B%4%%RxB34DjG;aJiM|Py2~~d9Of>mMR|}?7t{!ZjA$Xm za=}dzND&MwRKZ$Y^Aw5`%kndeK=Xu<*%Nrx3+sjA-i4c-3Tn{7(gdgu)&sS((M?QF zEdlj|AX}ut*7|@t|Im~Q+6Bn~F6^M|HNi{p!OCC*TJZiSc-R5500p!MH#HB`UOBuH zw9gAV4FPT1fVG2`fu(}f6_-HT_6o=YU7$W}5vZS-gHolZq!yHB7K8g$p+P6_c9Iyne;|=1KRxkvog8Kb=si2M?hy!ZWgW?#>cLuR4KugMf^3(G{lL#rLnV_@@ z+9{k486oin*G;L&yL}6aGV_u%3leh_kjBSAJs?niNi9|=ErHBdf?F5)rEor|eFz?9 zfDH2_?u3R&st( zQED-0Y6LWJ1?s66fJPp!9W-o=7#|0FBOs>~GB*g@feaS_ISD%Z z=yG^v0ciUtDBnVdo&551!TS9`{rALD%uxeSYbG%dGzJZtSp{VX*uVgE$1;N>L;{|9 zA%h7Z&qA^oD4i%mR6#6-^@*^FK{P|#%b@zcQWI3^9NLXrF{lTb3K^n-6~LGpL8%)w zVhL^qLR}0VfPgs$I!%nzX3%^A=wQJ7(xSXf&_Q0wkd_lt$bb(J0LLdNhk&}lP<0AO zLyj=}^dKGrr3CO;F@y^p`hjXVyb>x3N!O5mAf)t4MD8v?%3A0|2eiWpH6J>F0vnkG z#Th*PqZ$s?hdTm1^T0|#d-NInK;;%_nl&#Iw1YY`EfZ2g7c=;k<{n;Il&=8VK@Cn> zp?Tm*Ptbf%ZhmGlc#f+Ww5K~Sl>yvcE>11Y0X1=<4I=QG3dqo7QEEvhC~JcTA0aIp zg_Oj+Vo+PD1X?LE_?H%d5{M#$KR9|*K{Tk00H;6j_G+*eNMx0kf%3jWT52A63JFqv zgVx-GMnMycD!~p8DFThJ6eJcgfHz!&#e9(_y}$y{p-99CGkT78gbedTlS67+X)&mW z1=@&RlzDh%326MX0JJ?Dv;ly@Co{FQ0=g#~Hq(kaZ3Nz+3>sAfjRc}Kz9AEckbTOj zMUaLPXr~K$7mdNKv=}tqmXTisDw7lniVm+#1CKK?1QdbBa8`m0PGtb?-UiQhgZxyM z3F>5LmVsTLp97f&N0R`D2>ARkkO82{H_$2_P^`h5dGNj{q)G#C1wXtJG#wA>zQcBQ zgK8KgHrQMYKB$KS-RXUJC8E&+4k07~P-6kK$`7O*v|Af=BrZ%1Xgp6<1KhcTNkIn6 zpgScYi5YA?xPK3uE^>yCi9#7*C2l#T6$(MY!JwP~nuPt;)PJR)n&j;@EA)2tDI<;7#I6tohyn7sMe-P-5S#T8$J{qdD2y~=K z38;+=9|=Sn(Sx@Y5i96njdoDPfaa2*z6FO;)wwoN}NYrK=R4r&R2+28AuW zCk@(^iqVG#7jmd2WYFQ2#rdV+P7sKdQ(Da6T3no12^uR0rFU@c3{Ka@klpGENL?9F zag3A=oT2ML7+?%gfd_3GfEQ&zvoWNLN6MadiVQ5)gvJ+SWi5Q8JTZpi*9nqkfOiK| zK}7{@Ub+Zv1AGyKGkEwKG_;H~`| zhA+!Nk%lymK$GMp$WaRRL|Hy4{!8;NrY5h%hwG1|MEokXQs7#m^}%&IET+ zz@2q43sixCniOT3Md_tEse15c54f?BlL&H1Q6{8+8+>>rjOkib1eym*KD-4~K|&{) zAp;qukb^Etiy)K@oIHDqN6Yza*&s6h%@_{ab{HV4#_E6Fbfb*pk92MEDd;uRwXfj}~_(Y+w> zLE+#40i76L1UChwi12V9Y+MO67Ydp9fOrMGI21C^3Go8lr;y0Q)^q~r5BNw`GOTKZ zHAhP;V5=|=uLQM5tr*ZoT^OFPYk1l-?djU-$l(PV$_3A!f{x<=hbd@a0@PtENCh9M z1)8CUGr(>Dtq@Cvq@rTboB-(1!J>RnZyc1^tU(?s&d&i404p*$LkBwGJvPwH6lk=r z0MuIqZ}rEbD+JU)1-G8^i&8+-14a4ZH4h~W=rW)#Ib71UxFGTH#{3)xN6=ApsS418 z0Kl$x1h2vXB|hlL9w?9$p&ew-%tml&R%%HxESWO|=a&{gYM5A( zSyBWFDk}!3qQo-LfQKSbOvhf6Epmw0GaOrErJ9)?(hPTqjL&NA2rM@&4bvCFfuG3i!Di3^#j1TR(ut@DNy|DYlTJO+e)78$Z06tV6BW*RI3fK5wF zEy@LT0zp9!nqNuJEP)JwfvPA_#~QSh0aUx?mqNv%-3p{t4v>h$t{c=xNlb^VxPC;^)V^Eo2GK*c4< z1)y3QH1-EN%rqY~(h45;0|f{;XMj#ifR+%48x%klG^j=euLlFAkv#DFBhVat0c3R& zxabER8d}2OmkJrh22b&TT2;_d55({>s3#2@K87x30!?Z{5_c*%v=L*R(81ech)5Ab zFlfXCd^AETc-RIs>ILa}!>V6UsN^J~u5j>$tXPKxWeKDf2Q@uFlRON*kmZ6PHYDq2 z7N>$25Q0QNje76^HK=`rm~;j!^Z_6FP^18!39&+~U69E^^F0vvk2ras3>@@ zrxZGv1&RuU4zR({!E8{402;SN76Y*f9h!x7=rl+4W8$q3y;2p* zQcEj9NeZ0WK=Y+#sh|U@U@Xv>8pv9Z%i$MHfC^ZKz*6w&8GKe2Jjryp0lcIX)T)3b z9r#%fh*KY+!Bbics(Gpq6(?wY3%E&^3tDRpx}69#F$CU!1iBdsddN4ZhYnw8om#91 z82|(osGx>6Y~&A=+#%r#9{~Utn((48wF;CJA+rampev7%YD9#Ypeb5V3kAH657fDX z<}=W(4C$qy*685{;E_|vv=n%5FGnFaA2hz80G&@M%P+=OWP*;@f-lVgjgNtjn}?ho z0UpwX4rRhynxGpBU{xceas!Q>!kRH4&w#2YyzT(4ct@(|VWR<%Nf7Wj0N7@bIpCTP zyb%g?YJ5p1cuEbFnLrbF@C{M0Jp+(9K-CBDE`g#3sVz_fT5JzWrQjiJkdZ|g3+}Pb zd4v1wHkqJP>kyZn*yv~4!Pn=*`}>Dirh?C=C@h5x7=ZU76qja}fu|BdRUkZA!M!8! zz!l281*r1_O)z*0AN2;KZmzsg|2y){9b(9y}ZU#5|zzV^^4i0i~ z;{-efhge4evbiV~Iz0;-X+e0Oa^0YlFmMM`GH9tIxIYK;G{ULW*e4d24_hY>nkj~L zJsBXaH+WLTe<%hx7(uBQT+_oBM$f%fD$MuXHnY)u=s$6 z5_lvRvO)*6ya}=b3S6*)vm>Z%gST^v8Nd>dxpb^D43L(~;gt-aoo%2c)`wR@+o15s zMx1;C3wubJ4PO2XwiHL528tKb%!LlXgUTSPnF$^Bqo$dVxf!tcKr>39aU)d?_?aA_ zDSYriI5Xt)ftT^UjYf%aK}H#Otho}7YZaSLRN zC}yIv>B`#3dV?hJ#K=z#TS_PzL8%Nb4DOw>XFgnpVVicnCy&3G(S7 z;7!1w(?ilzL8rol*W`h>PBMTh0MHr%2Jkc_WX7?WAyFY2RAMtELgxP%Kpij`8@$30 zH0K1`(udqE1v|MIzSiz zA$bOdUUJ+;ALQ7Sgj24!A@m}Lyjs#9C!&T{sb!i z0cycts5q+%M8BX4#DVfE3=AR+3=C>24E5lkw}vY4P+?$@U|?VfQ-Sz6Uj<@Ow+bW? z%}{~(Y`+Qvg9QTv!+8~m&p1>e;sUA=ixgBL4%JhI#Jv@ic2R|x=cmfRz{SA85UpAd z5lDj?Pz;r5R)tv5rwU0E$5kN~U4zp1pyJO|Awl{TYQaBMNQiK#L84Sj4U#M5)gbB| z)F4sfsRqdnQT1w&ASqCT_@G=3;^KBSh{Y43`~^_{DyYVtY7hsVhZ=lc4U$+Ns6l-8 zMGX=X|DpPL)fpI+7#J92)FBSDSBE&L-UZ6=RfoiRm^#EkF;F^L9TGxW>JS6V)geLK zst)n#M0JS67pp_^^=7DfXQATP)FD272G##Z9pW)A4Y2-t21yNw0&NY5j~q217I|tw z3=Y>|V2Ea5U`U7Z4{JaSxC}9X;gJT!L0>c=J_Ds>P&Q`Ogm_220>8%|EmefcKlip3zW1N800`Do)!axE&~HY0F+;+1xXuowZK8g zuv`n`u$@r(t5EqbS`dr=X+b=|qs_pe$H2fKsSODc4{eA8{InSu>Ot8pSR0ZmbF?8r zRI3fKs6`uMP@gu$!a3TIM79p9?hw?VliCoA&Ozzh+K`ZWp$+lSM{P({GUzZcs4_4x z2c`*hF23eT$*y@2%H`1nCK=hC8~Dpn0te@xf=PMW6`G zWnf_7(PLncXJBBc(u0K9EIo+&#d?q^T&)KQxjj(&m>wkGUx2Fn4Au8rkAXp*fq|i( zMW2B|n}LBrM<3$D2z^LUr|Uy3D%XctFdZsBUmxP*_4*J8Y}1EWxF5)iVI8BG}&>On0Te^ZFhnoJ=EPcVhJa4wX7 zXUf3f&%nT7Z^po2!oa}LZw3j_E6fx0upzD7LaTcVZp$Vz`(#z1U2BY1p|XOBLl;23kHTp z1_lOoD+Y!c3=9k_tsrT`$(n(|kb!|A%9?>eih+Tl#~Knv%dH^=(K+jS28JL828J)z zkX+zy11VCAZ6J-(ej5e`Ed~aLEjADh_iY#$Di|0TSZx^?N*EXz>Y(&zTLy-31_p)< zI|c?H1_p)$b`X#7+Cv;7W)I=(*+V>JZVyQlcJ=mAaBg7+L93keh zJ3&H#-wEOXbtj0pr4!iPdIm=)NWKkng7`Gu36koIoggl6c7k*$dYu>;%t4J%CrH#h zc7phT!x`cMVP}ZLq@2M9GZ;8S%yoqF!=Q9JRJ_6&WPUvZL$5Q$2h*J)LB1L+!N9P~ z8Il%`I758!0IKeXGo(o6ae)*(`YsR$`?xSL7&9<1#JE5lFu{d^!IFW2VWA7e$G2S| zA@dk2|ILMgK@ODv*}Q6<^}Yz~IQhz_1&t@2@K) zMEKnx9+7f`cudm`60){#ARjX@ctXX4-540^K|PjqH%Kb4a)YGOrEZX*TjvJp@f?O4 z@YD?w5+9*_PIpLiUDO>?e(1SFQ&j1L8mf4@jI_Lir9J5TAN_K+1_&4~PZj9uNn1Lg_^w zkVLv0s_&r(sQ<^n!0^fg64#8LkRX=zg!shD6Cxhu3CVtWo)8E1c|sgK3#x9dCj&zg z0|UcRPe^^Q;RQ(p?p}~Y8Q}#<#Ccwjc0!LA#N3JXUJ#$G^@0S^7B5JVy2}fasE&I< z;_N!qz=vLt5cmW&@V6JlA(GyZDAe(WSQzdNNjtgT5Qn#TL!xMjH>4!oA0$NV z{2=-~{2(P@q#wlRRelhkPw<0;=yE@ZIcxnOKHusGanJ=nP)OG^FnscZ`0&3UB<>~r zAucubheS;>l+N;pxV+FG5;e6@x)&-x*B_E*miR+Fum?&X^@lj*oIfP)ulO@CFflMN z-1LX`|L^!iQtK20#1l4yc z0OBC#K!^u;0wH{fKn8|-(AbS)AY?4ZG7#dBtUyRAt_g%BuGxW*Y_%>B5`-rLA^G}o zASB4&1wtIk62!nz%D})N90X}xb_PKlxG@M4!iRz&iSc$2Buah!&C?q6gLLrG) zJ(PjLm4SgFClu1I*c1v0G1f3hqEiTigoG26j;;@bB#x3Wh(oHvAO^OCK@wws7$nHn zgh7J%3{?I$RQ_2QB*d7*A@aiEknE`x4haFXa7dc44TtzRG#t_*$_j_*tDh4Nv3L!X z-WCoCfdkWBhxVw5(52^ zkPy5Y3GvV)DE&4P5+Xk$!49lv5RQVxwNwGM$x3}y@r49d|EA4Ww( zQgLQ914AkU14C&v14ANcz!6H@#z3NCehkFiCozyT^EL*ejveuA%HtrZwE;?Z#6cV|Ar2CkbD-+B#zC51`{EcFB0%GQagdO8 zjAvkAgbb!K@G?Tuk3S;=!z%^`h5}Ii4;q(IV1%S(aYhD)^$ZLQoeT^Nr$E8V$iU#n z$iQ%nfq}t@k%3_z0|Uci1_lNWMh1qxpiqID{17z63K|WCS_Gm%V}p}G3@8Q-@m^+N zV3@|hz~ICH3B7t@r~#m{UywQ%Mh1o=5Cepvav&xQZ-6qi86jB;)P4X7=`k`ecz_t7 zaXm&zc>#)+6QEj=fq_Au5mNdEFfuSKgQ^4NXmFoz7gPW=(A>+wz!1p5z~BuPTfxA< zFr9&cAr#c?Msm;x1_p-xpixq&IuP}dfq`K%0|Ub>5Ql+*p_+k#p#jPUHO2mdde96E z44zPPIzZhTP&?j~5z>RX$H2f~$H2hQ1l0r@9R)FM7$GGjhz-JX7#J8PfO{7$DhV8>sgJN)S*nUIvDGa8pE?fq`K* z0|UccsDw2m1A`gV(v1v|j@(^PGKI>6C{R}iG~xtegYXsx28J693=Be0eIPM&Mh1r0 z3=9m8j0_C(85kIJ85tO!FfcGY07)`1Fo-eMGcfFga+ZPuoRNXy83O~uaRvqk3q}Tp zN(Kf7PDTcXPoPu*RR^QifKm?wq$E*gWMEjp0Le>MFa=QhB?AM)5(Wl_OHlrE21uR+ z)vHIqd5V#N!H|)GfsK)Yp$w`BL~UnaU|?rtV9;k^VED%X=}&{YwmU%mW6(4R0|Uch z1_lN>Mo66t(gVV4K?D?o=1cM!7#L(3A=M;Q7(BEEni{F^hl<>S(zc9{>I=lZ!oa{# z!@$6B9>f7P-a%t843LtekO9(r1a)OW-BwUn_7G^W2defdsQbgfz|g|Lz>o$in;9Ur z(q>TC7OIAuk)a+uQ6tF6z~Ictzz_$O08uv?7#J9#arq6@)&dDaF=$T3kCA~Pn}LDB z8I%V>NgkBR86lN<4pbe80#$Kbj0_BXj0_C_K>`d64A(*ZZU#^p4hnw;28Jo1fqVuA zhBr_JFiI5afS(Ku3_n0VXJlZ|VTAOZKx#mpybKV7fq_AUk%7UU0Wuu|6#`T4pwfmB zQbSz?i!d;(V}R6D3{d;Q!l2^0Pq2AR`0AX9fm_W>A>|>P>>CEf^qGBuFg? z-(g^2V1nw20pe@Uz`(Evlw%ke7$$~@&|>m6L;lIf4ZA0wGSZ){XY4+CPNu--9mZXZllPiBga&*1Sur>!<(HNylw@QU zD-@>|m8BLjI3_2j7L0lYJ~gC$G0q;4VunNi4Era7s+hp6qC;KDpddZt{D} zrpawqA(LNN@l5_@WyYG9mRXcLIl+1ZTWL{ZNovYuRa?!;3APTCci8Goeq&oWIo(c0 z)HNluM8UDNBqP6w0n7%uX!2dV@X7x6Mw|+XDJey%#l=>WSK2F0eqgW4otnbnmYJ7X zoS~pJ`J=HoqtfIe2mi^B9lWJ7Q}a@CDjD2U^HM?9Dfp$9D|qIWWtOB)4s+C+ywy=% z+%wq8Pr=Z@iUGnfhA^xqXFAnRe(#jeU(Dc@pP8qis*#tNo2pqn`IU1%PjQJtYD#8F zYKlTq<>Xf`UM%^^i8+&NTT?sAuz%;MoXInYCD za+^oeeW;>SO~Sk;xXmav~)~l?s_9sky}rL8)b#spSm8iDi>Z{Zc2t@spqI=6_5qBQ>Wi zHH9HKBfnfBF(*f%v;btyWQzbR{>=0|h0p?q;QX|b^2DOl$?Sn)lRE;PC*KT|W-m?3 zN=+^)o~#n&Ik`PZbn>?#=gBF-M<&k+k)C`%M0T=$sK(^{P#M{r%@tC`wICnf!5<$mG9FU}$1xV!ZiV zk~Gs~jZ}Wd;>oOOW|O_s949|aTg>P(Std#Z1Ea}zUj z6jD+r7ZwXow$GED+>$3U`B0w5WZ8Vp$ujxglWX(s1s%be1gt(NwIsi^XmUz{#pF!| z3XJ8GjS8nszE_w#Ilf42a!rv3J2%}+VJvgq*2$@Qg{lkb(rOpYuw znmnbfVX{KG_T<`fP4-lUoXoPK)XC4vCr+MRVJhed&LpV{sUT;8ES&teLVt2lrOo6o zl}3}Ts^TW^t;(NlR;@8Pt=f4qbB*a_{~E=~-)lBa-dMX{C@3{6KQlcqwF;5&9qVf* z->H|MT-1;UN$n2YC5a`O`FRX(Ii(c}LBYWce)%P-MGSuVlhZnVCm-ny=78u&4Kq_=DpHFWeDc#XlQUCu zCV!cbGI_?t*vVXz6j@8NQcH>_r%Z~Rd}or-Wber>lmAS~Ls))hkwS545lR+0J;Qf$(#)*M^JYm+-Z^XUWZO9*lWpd@O`bV7 zZnDsP&CPl9T^ToTU$B^Qvf>i6$+Ge6lNFY-OfJY&+?>8xm{F~mp&-8$l0Om^k~2#V XuVhG6$jb-OIgc7\n" "Language-Team: Mouse Reeve \n" "Language: fr_FR\n" @@ -20,74 +20,72 @@ msgstr "" #: bookwyrm/forms.py:226 msgid "A user with this email already exists." -msgstr "" +msgstr "Cet email est déjà associé à un compte." #: bookwyrm/forms.py:240 msgid "One Day" -msgstr "" +msgstr "Un jour" #: bookwyrm/forms.py:241 msgid "One Week" -msgstr "" +msgstr "Une semaine" #: bookwyrm/forms.py:242 msgid "One Month" -msgstr "" +msgstr "Un mois" #: bookwyrm/forms.py:243 msgid "Does Not Expire" -msgstr "" +msgstr "Sans expiration" #: bookwyrm/forms.py:248 #, python-format msgid "%(count)d uses" -msgstr "" +msgstr "%(count)d utilisations" #: bookwyrm/forms.py:251 -#, fuzzy #| msgid "Unlisted" msgid "Unlimited" -msgstr "Non listé" +msgstr "Sans limite" #: bookwyrm/models/fields.py:24 #, python-format msgid "%(value)s is not a valid remote_id" -msgstr "" +msgstr "%(value)s n’est pas une remote_id valide." #: bookwyrm/models/fields.py:33 bookwyrm/models/fields.py:42 #, python-format msgid "%(value)s is not a valid username" -msgstr "" +msgstr "%(value)s n’est pas un nom de compte valide." #: bookwyrm/models/fields.py:165 bookwyrm/templates/layout.html:152 -#, fuzzy #| msgid "Username:" msgid "username" -msgstr "Nom d’utilisateur :" +msgstr "nom du compte :" #: bookwyrm/models/fields.py:170 msgid "A user with that username already exists." -msgstr "" +msgstr "Ce nom est déjà associé à un compte." #: bookwyrm/settings.py:150 msgid "English" -msgstr "" +msgstr "English" #: bookwyrm/settings.py:151 msgid "German" -msgstr "" +msgstr "Deutsch" #: bookwyrm/settings.py:152 msgid "Spanish" -msgstr "" +msgstr "Español" #: bookwyrm/settings.py:153 msgid "French" -msgstr "" +msgstr "Français" #: bookwyrm/settings.py:154 msgid "Simplified Chinese" -msgstr "" +msgstr "简化字" #: bookwyrm/templates/404.html:4 bookwyrm/templates/404.html:8 msgid "Not Found" @@ -126,7 +124,7 @@ msgstr "Livres par %(name)s" #: bookwyrm/templates/discover/large-book.html:12 #: bookwyrm/templates/discover/small-book.html:9 msgid "by" -msgstr "" +msgstr "par" #: bookwyrm/templates/book/book.html:29 bookwyrm/templates/book/book.html:30 msgid "Edit Book" @@ -138,22 +136,21 @@ msgid "Add cover" msgstr "Ajouter une couverture" #: bookwyrm/templates/book/book.html:53 -#, fuzzy #| msgid "Failed to load" msgid "Failed to load cover" -msgstr "Items non importés" +msgstr "La couverture n’a pu être chargée" #: bookwyrm/templates/book/book.html:62 msgid "ISBN:" msgstr "ISBN :" #: bookwyrm/templates/book/book.html:69 -#: bookwyrm/templates/book/edit_book.html:211 +#: bookwyrm/templates/book/edit_book.html:217 msgid "OCLC Number:" msgstr "Numéro OCLC :" #: bookwyrm/templates/book/book.html:76 -#: bookwyrm/templates/book/edit_book.html:215 +#: bookwyrm/templates/book/edit_book.html:221 msgid "ASIN:" msgstr "ASIN :" @@ -165,11 +162,10 @@ msgstr "Voir sur OpenLibrary" #, python-format msgid "(%(review_count)s review)" msgid_plural "(%(review_count)s reviews)" -msgstr[0] "" -msgstr[1] "" +msgstr[0] "(%(review_count)s critique)" +msgstr[1] "(%(review_count)s critiques)" #: bookwyrm/templates/book/book.html:100 -#, fuzzy #| msgid "Description:" msgid "Add Description" msgstr "Ajouter une description" @@ -181,7 +177,7 @@ msgid "Description:" msgstr "Description :" #: bookwyrm/templates/book/book.html:111 -#: bookwyrm/templates/book/edit_book.html:225 +#: bookwyrm/templates/book/edit_book.html:231 #: bookwyrm/templates/edit_author.html:78 bookwyrm/templates/lists/form.html:42 #: bookwyrm/templates/preferences/edit_user.html:70 #: bookwyrm/templates/settings/site.html:93 @@ -194,7 +190,7 @@ msgstr "Enregistrer" #: bookwyrm/templates/book/book.html:112 bookwyrm/templates/book/book.html:161 #: bookwyrm/templates/book/cover_modal.html:32 -#: bookwyrm/templates/book/edit_book.html:226 +#: bookwyrm/templates/book/edit_book.html:232 #: bookwyrm/templates/edit_author.html:79 #: bookwyrm/templates/moderation/report_modal.html:32 #: bookwyrm/templates/snippets/delete_readthrough_modal.html:17 @@ -208,29 +204,28 @@ msgid "Cancel" msgstr "Annuler" #: bookwyrm/templates/book/book.html:121 -#, fuzzy, python-format +#, python-format #| msgid "Editions of \"%(work_title)s\"" msgid "%(count)s editions" -msgstr "%(title)s par " +msgstr "%(count)s éditions" #: bookwyrm/templates/book/book.html:129 -#, fuzzy, python-format +#, python-format #| msgid "favorited your %(preview_name)s" msgid "This edition is on your %(shelf_name)s shelf." -msgstr "Messages directs avec %(username)s" +msgstr "Cette édition est sur votre étagère %(shelf_name)s." #: bookwyrm/templates/book/book.html:135 -#, fuzzy, python-format +#, python-format #| msgid "replied to your %(preview_name)s" msgid "A different edition of this book is on your %(shelf_name)s shelf." -msgstr " a ajouté %(book_title)s à votre liste « %(list_name)s »" +msgstr "Une édition différente de ce livre existe sur votre étagère %(shelf_name)s." #: bookwyrm/templates/book/book.html:144 msgid "Your reading activity" msgstr "Votre activité de lecture" #: bookwyrm/templates/book/book.html:146 -#, fuzzy #| msgid "Edit read dates" msgid "Add read dates" msgstr "Ajouter des dates de lecture" @@ -259,10 +254,9 @@ msgid "Lists" msgstr "Listes" #: bookwyrm/templates/book/book.html:213 -#, fuzzy #| msgid "Go to list" msgid "Add to list" -msgstr "Aller à la liste" +msgstr "Ajouter à la liste" #: bookwyrm/templates/book/book.html:223 #: bookwyrm/templates/book/cover_modal.html:31 @@ -275,30 +269,28 @@ msgid "rated it" msgstr "l’a noté" #: bookwyrm/templates/book/cover_modal.html:17 -#: bookwyrm/templates/book/edit_book.html:163 -#, fuzzy +#: bookwyrm/templates/book/edit_book.html:169 #| msgid "Add cover" msgid "Upload cover:" -msgstr "Ajouter une couverture" +msgstr "Charger une couverture :" #: bookwyrm/templates/book/cover_modal.html:23 -#: bookwyrm/templates/book/edit_book.html:169 +#: bookwyrm/templates/book/edit_book.html:175 msgid "Load cover from url:" -msgstr "" +msgstr "Charger la couverture depuis une URL :" #: bookwyrm/templates/book/edit_book.html:5 #: bookwyrm/templates/book/edit_book.html:11 -#, fuzzy, python-format +#, python-format #| msgid "Finish \"%(book_title)s\"" msgid "Edit \"%(book_title)s\"" -msgstr "Éditions de %(book_title)s" +msgstr "Modifier « %(book_title)s »" #: bookwyrm/templates/book/edit_book.html:5 #: bookwyrm/templates/book/edit_book.html:13 -#, fuzzy #| msgid "Add Books" msgid "Add Book" -msgstr "Ajouter des livres" +msgstr "Ajouter un livre" #: bookwyrm/templates/book/edit_book.html:18 #: bookwyrm/templates/edit_author.html:13 @@ -317,35 +309,35 @@ msgstr "Dernière modification par :" #: bookwyrm/templates/book/edit_book.html:40 msgid "Confirm Book Info" -msgstr "" +msgstr "Confirmer les informations de ce livre" #: bookwyrm/templates/book/edit_book.html:47 #, python-format msgid "Is \"%(name)s\" an existing author?" -msgstr "" +msgstr "Est‑ce que l’auteur ou l’autrice « %(name)s » existe déjà ?" #: bookwyrm/templates/book/edit_book.html:52 -#, fuzzy, python-format +#, python-format #| msgid "Start \"%(book_title)s\"" msgid "Author of %(book_title)s" msgstr "Commencer « %(book_title)s »" #: bookwyrm/templates/book/edit_book.html:55 msgid "This is a new author" -msgstr "" +msgstr "Il s’agit d’un nouvel auteur ou d’une nouvelle autrice." #: bookwyrm/templates/book/edit_book.html:61 #, python-format msgid "Creating a new author: %(name)s" -msgstr "" +msgstr "Création d’un nouvel auteur ou d’une nouvelle autrice : %(name)s" #: bookwyrm/templates/book/edit_book.html:67 msgid "Is this an edition of an existing work?" -msgstr "" +msgstr "Est‑ce l’édition d’un ouvrage existant ?" #: bookwyrm/templates/book/edit_book.html:71 msgid "This is a new work" -msgstr "" +msgstr "Il s’agit d’un nouvel ouvrage." #: bookwyrm/templates/book/edit_book.html:77 #: bookwyrm/templates/password_reset.html:30 @@ -379,82 +371,79 @@ msgid "Series number:" msgstr "Numéro dans la série :" #: bookwyrm/templates/book/edit_book.html:117 -#, fuzzy #| msgid "Published" msgid "Publisher:" -msgstr "Publié" +msgstr "Éditeur :" #: bookwyrm/templates/book/edit_book.html:119 msgid "Separate multiple publishers with commas." -msgstr "" +msgstr "Séparez plusieurs éditeurs par une virgule." -#: bookwyrm/templates/book/edit_book.html:125 +#: bookwyrm/templates/book/edit_book.html:126 msgid "First published date:" msgstr "Première date de publication :" -#: bookwyrm/templates/book/edit_book.html:130 +#: bookwyrm/templates/book/edit_book.html:134 msgid "Published date:" msgstr "Date de publication :" -#: bookwyrm/templates/book/edit_book.html:137 -#, fuzzy +#: bookwyrm/templates/book/edit_book.html:143 #| msgid "Author" msgid "Authors" -msgstr "Auteur ou autrice" - -#: bookwyrm/templates/book/edit_book.html:143 -#, fuzzy, python-format -#| msgid "favorited your %(preview_name)s" -msgid "Remove %(name)s" -msgstr "Messages directs avec %(username)s" - -#: bookwyrm/templates/book/edit_book.html:148 -#, fuzzy -#| msgid "Edit Author" -msgid "Add Authors:" -msgstr "Modifier l’auteur ou autrice" +msgstr "Auteurs ou autrices" #: bookwyrm/templates/book/edit_book.html:149 -msgid "John Doe, Jane Smith" -msgstr "" +#, python-format +#| msgid "favorited your %(preview_name)s" +msgid "Remove %(name)s" +msgstr "Supprimer %(name)s" + +#: bookwyrm/templates/book/edit_book.html:154 +#| msgid "Edit Author" +msgid "Add Authors:" +msgstr "Ajouter des auteurs ou autrices :" #: bookwyrm/templates/book/edit_book.html:155 +msgid "John Doe, Jane Smith" +msgstr "Claude Dupont, Dominique Durand" + +#: bookwyrm/templates/book/edit_book.html:161 #: bookwyrm/templates/user/shelf.html:75 msgid "Cover" msgstr "Couverture" -#: bookwyrm/templates/book/edit_book.html:182 +#: bookwyrm/templates/book/edit_book.html:188 msgid "Physical Properties" msgstr "Propriétés physiques" -#: bookwyrm/templates/book/edit_book.html:183 +#: bookwyrm/templates/book/edit_book.html:189 #: bookwyrm/templates/book/format_filter.html:5 msgid "Format:" msgstr "Format :" -#: bookwyrm/templates/book/edit_book.html:191 +#: bookwyrm/templates/book/edit_book.html:197 msgid "Pages:" msgstr "Pages :" -#: bookwyrm/templates/book/edit_book.html:198 +#: bookwyrm/templates/book/edit_book.html:204 msgid "Book Identifiers" msgstr "Identifiants du livre" -#: bookwyrm/templates/book/edit_book.html:199 +#: bookwyrm/templates/book/edit_book.html:205 msgid "ISBN 13:" msgstr "ISBN 13 :" -#: bookwyrm/templates/book/edit_book.html:203 +#: bookwyrm/templates/book/edit_book.html:209 msgid "ISBN 10:" msgstr "ISBN 10 :" -#: bookwyrm/templates/book/edit_book.html:207 +#: bookwyrm/templates/book/edit_book.html:213 #: bookwyrm/templates/edit_author.html:59 msgid "Openlibrary key:" msgstr "Clé Openlibrary :" #: bookwyrm/templates/book/editions.html:5 -#, fuzzy, python-format +#, python-format #| msgid "Finish \"%(book_title)s\"" msgid "Editions of %(book_title)s" msgstr "Éditions de %(book_title)s" @@ -467,89 +456,90 @@ msgstr "Éditions de « %(work_title)s »" #: bookwyrm/templates/book/format_filter.html:8 #: bookwyrm/templates/book/language_filter.html:8 msgid "Any" -msgstr "" +msgstr "Tou(te)s" #: bookwyrm/templates/book/language_filter.html:5 msgid "Language:" -msgstr "" +msgstr "Langue :" #: bookwyrm/templates/book/publisher_info.html:6 -#, fuzzy, python-format +#, python-format #| msgid "of %(book.pages)s pages" msgid "%(format)s, %(pages)s pages" -msgstr "sur %(book.pages)s pages" +msgstr "%(format)s, %(pages)s pages" #: bookwyrm/templates/book/publisher_info.html:8 -#, fuzzy, python-format +#, python-format #| msgid "of %(book.pages)s pages" msgid "%(pages)s pages" -msgstr "sur %(book.pages)s pages" +msgstr "%(pages)s pages" #: bookwyrm/templates/book/publisher_info.html:13 -#, fuzzy, python-format +#, python-format #| msgid "of %(book.pages)s pages" msgid "%(languages)s language" -msgstr "sur %(book.pages)s pages" +msgstr "%(languages)s langues" #: bookwyrm/templates/book/publisher_info.html:18 #, python-format msgid "Published %(date)s by %(publisher)s." -msgstr "" +msgstr "Publié %(date)s par %(publisher)s." #: bookwyrm/templates/book/publisher_info.html:20 -#, fuzzy, python-format +#, python-format #| msgid "Published date:" msgid "Published %(date)s" -msgstr "Date de publication :" +msgstr "Publié %(date)s" #: bookwyrm/templates/book/publisher_info.html:22 #, python-format msgid "Published by %(publisher)s." -msgstr "" +msgstr "Publié par %(publisher)s." #: bookwyrm/templates/components/inline_form.html:8 #: bookwyrm/templates/components/modal.html:11 -#: bookwyrm/templates/feed/feed_layout.html:57 +#: bookwyrm/templates/feed/feed_layout.html:70 #: bookwyrm/templates/get_started/layout.html:19 #: bookwyrm/templates/get_started/layout.html:52 -#, fuzzy #| msgid "Closed" msgid "Close" msgstr "Fermer" +#: bookwyrm/templates/compose.html:5 bookwyrm/templates/compose.html:8 +#| msgid "Boost status" +msgid "Compose status" +msgstr "Rédiger un statut" + #: bookwyrm/templates/directory/community_filter.html:5 -#, fuzzy #| msgid "Comment" msgid "Community" -msgstr "Commentaire" +msgstr "Communauté" #: bookwyrm/templates/directory/community_filter.html:8 -#, fuzzy #| msgid "Blocked users" msgid "Local users" -msgstr "Comptes bloqués" +msgstr "Comptes locaux" #: bookwyrm/templates/directory/community_filter.html:12 -#, fuzzy #| msgid "Federated" msgid "Federated community" -msgstr "Fédéré" +msgstr "Communauté fédérée" #: bookwyrm/templates/directory/directory.html:6 #: bookwyrm/templates/directory/directory.html:11 #: bookwyrm/templates/layout.html:92 msgid "Directory" -msgstr "" +msgstr "Répertoire" #: bookwyrm/templates/directory/directory.html:19 msgid "Make your profile discoverable to other BookWyrm users." -msgstr "" +msgstr "Autoriser d’autres utilisateurs ou utilisatrices de BookWyrm à découvrir votre profil." #: bookwyrm/templates/directory/directory.html:26 -#, fuzzy, python-format +#, python-format #| msgid "You can set or change your reading goal any time from your profile page" msgid "You can opt-out at any time in your profile settings." -msgstr "Vous pouvez définir ou changer vore défi lecture à n’importe quel moment depuis votre profil" +msgstr "Vous pouvez décider de ne plus y figurer à n’importe quel moment depuis vos paramètres de profil." #: bookwyrm/templates/directory/directory.html:31 #: bookwyrm/templates/snippets/goal_card.html:22 @@ -557,64 +547,59 @@ msgid "Dismiss message" msgstr "Rejeter le message" #: bookwyrm/templates/directory/directory.html:71 -#, fuzzy #| msgid "followed you" msgid "follower you follow" msgid_plural "followers you follow" -msgstr[0] "s’est abonné(e)" -msgstr[1] "s’est abonné(e)" +msgstr[0] "compte auquel vous êtes abonné(e)" +msgstr[1] "comptes auxquels vous êtes abonné(e)" #: bookwyrm/templates/directory/directory.html:78 -#, fuzzy #| msgid "Your shelves" msgid "book on your shelves" msgid_plural "books on your shelves" -msgstr[0] "Vos étagères" -msgstr[1] "Vos étagères" +msgstr[0] "livre sur vos étagères" +msgstr[1] "livres sur vos étagères" #: bookwyrm/templates/directory/directory.html:86 msgid "posts" -msgstr "" +msgstr "publications" #: bookwyrm/templates/directory/directory.html:92 msgid "last active" -msgstr "" +msgstr "dernière activité" #: bookwyrm/templates/directory/sort_filter.html:5 msgid "Order by" -msgstr "" +msgstr "Trier par" #: bookwyrm/templates/directory/sort_filter.html:8 -#, fuzzy #| msgid "Suggest" msgid "Suggested" -msgstr "Suggérer" +msgstr "Suggéré" #: bookwyrm/templates/directory/sort_filter.html:9 msgid "Recently active" -msgstr "" +msgstr "Actif récemment" #: bookwyrm/templates/directory/user_type_filter.html:5 -#, fuzzy #| msgid "User Activity" msgid "User type" -msgstr "Activité du compte" +msgstr "Type de compte" #: bookwyrm/templates/directory/user_type_filter.html:8 -#, fuzzy #| msgid "Blocked users" msgid "BookWyrm users" -msgstr "Comptes bloqués" +msgstr "Comptes BookWyrm" #: bookwyrm/templates/directory/user_type_filter.html:12 msgid "All known users" -msgstr "" +msgstr "Tous les comptes connus" #: bookwyrm/templates/discover/about.html:7 -#, fuzzy, python-format +#, python-format #| msgid "Join %(name)s" msgid "About %(site_name)s" -msgstr "À propos de %(name)s" +msgstr "À propos de %(site_name)s" #: bookwyrm/templates/discover/about.html:10 #: bookwyrm/templates/discover/about.html:20 @@ -659,11 +644,11 @@ msgstr "Cette instance est fermée" #: bookwyrm/templates/discover/landing_layout.html:57 msgid "Thank you! Your request has been received." -msgstr "" +msgstr "Merci ! Votre demande a bien été reçue." #: bookwyrm/templates/discover/landing_layout.html:60 msgid "Request an Invitation" -msgstr "" +msgstr "Demander une invitation" #: bookwyrm/templates/discover/landing_layout.html:64 #: bookwyrm/templates/password_reset_request.html:18 @@ -675,17 +660,16 @@ msgstr "Adresse email :" #: bookwyrm/templates/discover/landing_layout.html:70 #: bookwyrm/templates/moderation/report_modal.html:31 msgid "Submit" -msgstr "" +msgstr "Valider" #: bookwyrm/templates/discover/landing_layout.html:79 msgid "Your Account" msgstr "Votre compte" #: bookwyrm/templates/edit_author.html:5 -#, fuzzy #| msgid "Edit Author" msgid "Edit Author:" -msgstr "Modifier l’auteur ou autrice" +msgstr "Modifier l’auteur ou l’autrice :" #: bookwyrm/templates/edit_author.html:32 bookwyrm/templates/lists/form.html:8 #: bookwyrm/templates/user/create_shelf_form.html:13 @@ -724,49 +708,48 @@ msgstr "Clé Goodreads :" #: bookwyrm/templates/email/html_layout.html:15 #: bookwyrm/templates/email/text_layout.html:2 msgid "Hi there," -msgstr "" +msgstr "Bien le bonjour," #: bookwyrm/templates/email/html_layout.html:21 #, python-format msgid "BookWyrm hosted on %(site_name)s" -msgstr "" +msgstr "BookWyrm, hébergé par %(site_name)s" #: bookwyrm/templates/email/html_layout.html:23 msgid "Email preference" -msgstr "" +msgstr "Paramètres d’email" #: bookwyrm/templates/email/invite/html_content.html:6 #: bookwyrm/templates/email/invite/subject.html:2 -#, fuzzy, python-format +#, python-format #| msgid "Join %(name)s" msgid "You're invited to join %(site_name)s!" -msgstr "À propos de %(name)s" +msgstr "Vous avez reçu une invitation à rejoindre %(site_name)s !" #: bookwyrm/templates/email/invite/html_content.html:9 msgid "Join Now" -msgstr "" +msgstr "S’enregistrer maintenant" #: bookwyrm/templates/email/invite/html_content.html:15 #, python-format msgid "Learn more about this instance." -msgstr "" +msgstr "En savoir plus sur cette instance." #: bookwyrm/templates/email/invite/text_content.html:4 #, python-format msgid "You're invited to join %(site_name)s! Click the link below to create an account." -msgstr "" +msgstr "Vous avez reçu une invitation à rejoindre %(site_name)s ! Cliquez le lien suivant pour créer un compte." #: bookwyrm/templates/email/invite/text_content.html:8 -#, fuzzy #| msgid "More about this site" msgid "Learn more about this instance:" -msgstr "En savoir plus sur ce site" +msgstr "En savoir plus sur cete instance :" #: bookwyrm/templates/email/password_reset/html_content.html:6 #: bookwyrm/templates/email/password_reset/text_content.html:4 #, python-format msgid "You requested to reset your %(site_name)s password. Click the link below to set a new password and log in to your account." -msgstr "" +msgstr "Une demande de réinitialisation de votre mot de passe sur %(site_name)s a été initialisée. Cliquez le lien suivant pour définir un nouveau mot de passe et vous connecter à votre compte." #: bookwyrm/templates/email/password_reset/html_content.html:9 #: bookwyrm/templates/password_reset.html:4 @@ -779,13 +762,13 @@ msgstr "Changez le mot de passe" #: bookwyrm/templates/email/password_reset/html_content.html:13 #: bookwyrm/templates/email/password_reset/text_content.html:8 msgid "If you didn't request to reset your password, you can ignore this email." -msgstr "" +msgstr "Si vous n’avez pas demandé la réinitialisation de votre mot de passe, vous pouvez ignorer cet email." #: bookwyrm/templates/email/password_reset/subject.html:2 -#, fuzzy, python-format +#, python-format #| msgid "Join %(name)s" msgid "Reset your %(site_name)s password" -msgstr "À propos de %(name)s" +msgstr "Réinitialiser votre mot de passe sur %(site_name)s" #: bookwyrm/templates/feed/direct_messages.html:8 #, python-format @@ -807,19 +790,17 @@ msgstr "Vous n’avez aucun message pour l’instant." #: bookwyrm/templates/feed/feed.html:9 msgid "Home Timeline" -msgstr "" +msgstr "Mon fil d’actualité" #: bookwyrm/templates/feed/feed.html:11 -#, fuzzy #| msgid "%(tab_title)s Timeline" msgid "Local Timeline" -msgstr "%(tab_title)s — Fil d’actualité" +msgstr "Fil d’actualité local" #: bookwyrm/templates/feed/feed.html:13 -#, fuzzy #| msgid "Federated Servers" msgid "Federated Timeline" -msgstr "Serveurs fédérés" +msgstr "Fil d’actualité des instances fédérées" #: bookwyrm/templates/feed/feed.html:19 msgid "Home" @@ -836,7 +817,7 @@ msgstr "Fédéré" #: bookwyrm/templates/feed/feed.html:33 #, python-format msgid "load 0 unread status(es)" -msgstr "" +msgstr "charger le(s) 0 statut(s) non lu(s)" #: bookwyrm/templates/feed/feed.html:48 msgid "There aren't any activities right now! Try following a user to get started" @@ -845,10 +826,9 @@ msgstr "Aucune activité pour l’instant ! Abonnez‑vous à quelqu’un pour #: bookwyrm/templates/feed/feed.html:56 #: bookwyrm/templates/get_started/users.html:6 msgid "Who to follow" -msgstr "" +msgstr "À qui s’abonner" #: bookwyrm/templates/feed/feed_layout.html:5 -#, fuzzy #| msgid "Updated:" msgid "Updates" msgstr "Mises à jour" @@ -863,27 +843,25 @@ msgstr "Vos livres" msgid "There are no books here right now! Try searching for a book to get started" msgstr "Aucun livre ici pour l’instant ! Cherchez un livre pour commencer" -#: bookwyrm/templates/feed/feed_layout.html:23 -#: bookwyrm/templates/user/shelf.html:28 -#, fuzzy -#| msgid "Read" -msgid "To Read" -msgstr "Lu" - #: bookwyrm/templates/feed/feed_layout.html:24 #: bookwyrm/templates/user/shelf.html:28 -#, fuzzy -#| msgid "Started reading" -msgid "Currently Reading" -msgstr "Commencer la lecture" +#| msgid "Read" +msgid "To Read" +msgstr "À lire" #: bookwyrm/templates/feed/feed_layout.html:25 +#: bookwyrm/templates/user/shelf.html:28 +#| msgid "Started reading" +msgid "Currently Reading" +msgstr "En train de lire" + +#: bookwyrm/templates/feed/feed_layout.html:26 #: bookwyrm/templates/snippets/shelve_button/shelve_button_options.html:11 #: bookwyrm/templates/user/shelf.html:28 msgid "Read" msgstr "Lu" -#: bookwyrm/templates/feed/feed_layout.html:74 bookwyrm/templates/goal.html:26 +#: bookwyrm/templates/feed/feed_layout.html:88 bookwyrm/templates/goal.html:26 #: bookwyrm/templates/snippets/goal_card.html:6 #, python-format msgid "%(year)s Reading Goal" @@ -893,27 +871,26 @@ msgstr "Défi lecture pour %(year)s" #, python-format msgid "%(mutuals)s follower you follow" msgid_plural "%(mutuals)s followers you follow" -msgstr[0] "" -msgstr[1] "" +msgstr[0] "%(mutuals)s abonnement auxquel vous êtes abonné(e)" +msgstr[1] "%(mutuals)s abonnements auxquels vous êtes abonné(e)" #: bookwyrm/templates/feed/suggested_users.html:19 #, python-format msgid "%(shared_books)s book on your shelves" msgid_plural "%(shared_books)s books on your shelves" -msgstr[0] "" -msgstr[1] "" +msgstr[0] "%(shared_books)s livre sur vos étagères" +msgstr[1] "%(shared_books)s livres sur vos étagères" #: bookwyrm/templates/get_started/book_preview.html:6 -#, fuzzy, python-format +#, python-format #| msgid "Want to Read \"%(book_title)s\"" msgid "Have you read %(book_title)s?" -msgstr "A envie de lire « %(book_title)s »" +msgstr "Avez‑vous lu « %(book_title)s » ?" #: bookwyrm/templates/get_started/books.html:6 -#, fuzzy #| msgid "Started reading" msgid "What are you reading?" -msgstr "Lecture commencée le" +msgstr "Que lisez‑vous ?" #: bookwyrm/templates/get_started/books.html:9 #: bookwyrm/templates/lists/list.html:58 @@ -930,7 +907,7 @@ msgstr "Aucun livre trouvé pour « %(query)s »" #: bookwyrm/templates/get_started/books.html:11 #, python-format msgid "You can add books when you start using %(site_name)s." -msgstr "" +msgstr "Vous pourrez ajouter des livres lorsque vous commencerez à utiliser %(site_name)s." #: bookwyrm/templates/get_started/books.html:16 #: bookwyrm/templates/get_started/books.html:17 @@ -942,16 +919,15 @@ msgid "Search" msgstr "Chercher" #: bookwyrm/templates/get_started/books.html:26 -#, fuzzy #| msgid "Suggest Books" msgid "Suggested Books" msgstr "Suggérer des livres" #: bookwyrm/templates/get_started/books.html:41 -#, fuzzy, python-format +#, python-format #| msgid "Join %(name)s" msgid "Popular on %(site_name)s" -msgstr "À propos de %(name)s" +msgstr "Populaire sur %(site_name)s" #: bookwyrm/templates/get_started/books.html:51 #: bookwyrm/templates/lists/list.html:75 @@ -961,46 +937,42 @@ msgstr "Aucun livre trouvé" #: bookwyrm/templates/get_started/books.html:54 #: bookwyrm/templates/get_started/profile.html:54 msgid "Save & continue" -msgstr "" +msgstr "Enregistrer & continuer" #: bookwyrm/templates/get_started/layout.html:14 -#, fuzzy, python-format +#, python-format #| msgid "Join %(name)s" msgid "Welcome to %(site_name)s!" -msgstr "À propos de %(name)s" +msgstr "Bienvenu(e) sur %(site_name)s !" #: bookwyrm/templates/get_started/layout.html:16 msgid "These are some first steps to get you started." -msgstr "" +msgstr "Voici quelques étapes pour commencer votre profil." #: bookwyrm/templates/get_started/layout.html:30 #: bookwyrm/templates/get_started/profile.html:6 -#, fuzzy #| msgid "User Profile" msgid "Create your profile" -msgstr "Profil" +msgstr "Créez votre profil" #: bookwyrm/templates/get_started/layout.html:34 -#, fuzzy #| msgid "Add Books" msgid "Add books" -msgstr "Ajouter des livres" +msgstr "Ajoutez des livres" #: bookwyrm/templates/get_started/layout.html:38 -#, fuzzy #| msgid "Friendly" msgid "Find friends" -msgstr "Sympa" +msgstr "Établissez des contacts" #: bookwyrm/templates/get_started/layout.html:44 msgid "Skip this step" -msgstr "" +msgstr "Passer cette étape" #: bookwyrm/templates/get_started/layout.html:48 -#, fuzzy #| msgid "Finished" msgid "Finish" -msgstr "Terminé" +msgstr "Terminer" #: bookwyrm/templates/get_started/profile.html:15 #: bookwyrm/templates/preferences/edit_user.html:24 @@ -1014,7 +986,7 @@ msgstr "Résumé :" #: bookwyrm/templates/get_started/profile.html:23 msgid "A little bit about you" -msgstr "" +msgstr "Parlez‑nous de vous" #: bookwyrm/templates/get_started/profile.html:32 #: bookwyrm/templates/preferences/edit_user.html:17 @@ -1029,17 +1001,16 @@ msgstr "Autoriser les abonnements manuellement :" #: bookwyrm/templates/get_started/profile.html:48 #: bookwyrm/templates/preferences/edit_user.html:58 msgid "Show this account in suggested users:" -msgstr "" +msgstr "Afficher ce compte dans ceux suggérés :" #: bookwyrm/templates/get_started/profile.html:52 msgid "Your account will show up in the directory, and may be recommended to other BookWyrm users." -msgstr "" +msgstr "Votre compte sera listé dans le répertoire et pourra être recommandé à d’autres utilisateurs ou utilisatrices de BookWyrm." #: bookwyrm/templates/get_started/users.html:11 -#, fuzzy #| msgid "Search for a book or user" msgid "Search for a user" -msgstr "Chercher un livre ou un compte" +msgstr "Chercher un compte" #: bookwyrm/templates/get_started/users.html:13 #: bookwyrm/templates/search_results.html:76 @@ -1053,7 +1024,6 @@ msgid "%(year)s Reading Progress" msgstr "Progression de lecture pour %(year)s" #: bookwyrm/templates/goal.html:11 -#, fuzzy #| msgid "Edit Book" msgid "Edit Goal" msgstr "Modifier le défi" @@ -1070,13 +1040,13 @@ msgid "%(name)s hasn't set a reading goal for %(year)s." msgstr "%(name)s n’a aucun défi lecture pour %(year)s." #: bookwyrm/templates/goal.html:51 -#, fuzzy, python-format +#, python-format #| msgid "Your books" msgid "Your %(year)s Books" msgstr "Vos livres en %(year)s" #: bookwyrm/templates/goal.html:53 -#, fuzzy, python-format +#, python-format #| msgid "%(username)s has no followers" msgid "%(username)s's %(year)s Books" msgstr "Livres de %(username)s en %(year)s" @@ -1087,14 +1057,13 @@ msgid "Import Books" msgstr "Importer des livres" #: bookwyrm/templates/import.html:16 -#, fuzzy #| msgid "Data source" msgid "Data source:" -msgstr "Source de données" +msgstr "Source de données :" #: bookwyrm/templates/import.html:29 msgid "Data file:" -msgstr "" +msgstr "Fichier de données :" #: bookwyrm/templates/import.html:37 msgid "Include reviews" @@ -1224,10 +1193,9 @@ msgid "Feed" msgstr "Fil d’actualité" #: bookwyrm/templates/layout.html:102 -#, fuzzy #| msgid "Instance Settings" msgid "Settings" -msgstr "Paramètres de l’instance" +msgstr "Paramètres" #: bookwyrm/templates/layout.html:111 #: bookwyrm/templates/settings/admin_layout.html:24 @@ -1239,7 +1207,7 @@ msgstr "Invitations" #: bookwyrm/templates/layout.html:118 msgid "Admin" -msgstr "" +msgstr "Admin" #: bookwyrm/templates/layout.html:125 msgid "Log out" @@ -1255,13 +1223,12 @@ msgstr "Notifications" #: bookwyrm/templates/login.html:17 #: bookwyrm/templates/snippets/register_form.html:4 msgid "Username:" -msgstr "Nom d’utilisateur :" +msgstr "Nom du compte :" #: bookwyrm/templates/layout.html:156 -#, fuzzy #| msgid "Password:" msgid "password" -msgstr "Mot de passe :" +msgstr "Mot de passe" #: bookwyrm/templates/layout.html:157 bookwyrm/templates/login.html:36 msgid "Forgot your password?" @@ -1274,7 +1241,7 @@ msgstr "Se connecter" #: bookwyrm/templates/layout.html:168 msgid "Join" -msgstr "" +msgstr "Rejoindre" #: bookwyrm/templates/layout.html:191 msgid "About this server" @@ -1287,11 +1254,11 @@ msgstr "Contacter l’administrateur du site" #: bookwyrm/templates/layout.html:202 #, python-format msgid "Support %(site_name)s on %(support_title)s" -msgstr "" +msgstr "Soutenez %(site_name)s avec %(support_title)s" #: bookwyrm/templates/layout.html:206 msgid "BookWyrm is open source software. You can contribute or report issues on GitHub." -msgstr "Bookwyrm est un logiciel libre. Vous pouvez contribuer ou faire des rapports de bogues via GitHub." +msgstr "BookWyrm est un logiciel libre. Vous pouvez contribuer ou faire des rapports de bogues via GitHub." #: bookwyrm/templates/lists/create_form.html:5 #: bookwyrm/templates/lists/lists.html:19 @@ -1299,16 +1266,16 @@ msgid "Create List" msgstr "Créer une liste" #: bookwyrm/templates/lists/created_text.html:5 -#, fuzzy, python-format +#, python-format #| msgid "favorited your %(preview_name)s" msgid "Created and curated by %(username)s" -msgstr "Messages directs avec %(username)s" +msgstr "Créée et modérée par %(username)s" #: bookwyrm/templates/lists/created_text.html:7 -#, fuzzy, python-format +#, python-format #| msgid "favorited your %(preview_name)s" msgid "Created by %(username)s" -msgstr "Messages directs avec %(username)s" +msgstr "Créée par %(username)s" #: bookwyrm/templates/lists/curate.html:6 msgid "Pending Books" @@ -1349,7 +1316,7 @@ msgstr "Fermée" #: bookwyrm/templates/lists/form.html:22 msgid "Only you can add and remove books to this list" -msgstr "Vous seul(e) pouvez ajouter ou supprimer des livres dans cette liste" +msgstr "Vous seul(e) pouvez ajouter ou retirer des livres dans cette liste" #: bookwyrm/templates/lists/form.html:26 msgid "Curated" @@ -1373,10 +1340,10 @@ msgid "This list is currently empty" msgstr "Cette liste est vide actuellement" #: bookwyrm/templates/lists/list.html:35 -#, fuzzy, python-format +#, python-format #| msgid "favorited your %(preview_name)s" msgid "Added by %(username)s" -msgstr "Messages directs avec %(username)s" +msgstr "Ajoutée par %(username)s" #: bookwyrm/templates/lists/list.html:41 #: bookwyrm/templates/snippets/shelf_selector.html:26 @@ -1430,128 +1397,121 @@ msgstr "En savoir plus sur ce site" #: bookwyrm/templates/moderation/report_preview.html:6 #, python-format msgid "Report #%(report_id)s: %(username)s" -msgstr "" +msgstr "Signalement #%(report_id)s : %(username)s" #: bookwyrm/templates/moderation/report.html:10 msgid "Back to reports" -msgstr "" +msgstr "Retour aux signalements" #: bookwyrm/templates/moderation/report.html:18 -#, fuzzy #| msgid "Notifications" msgid "Actions" -msgstr "Notifications" +msgstr "Actions" #: bookwyrm/templates/moderation/report.html:19 -#, fuzzy #| msgid "User Profile" msgid "View user profile" -msgstr "Profil" +msgstr "Voir le profil" #: bookwyrm/templates/moderation/report.html:22 -#: bookwyrm/templates/snippets/status/status_options.html:25 +#: bookwyrm/templates/snippets/status/status_options.html:35 #: bookwyrm/templates/snippets/user_options.html:13 msgid "Send direct message" msgstr "Envoyer un message direct" #: bookwyrm/templates/moderation/report.html:27 msgid "Deactivate user" -msgstr "" +msgstr "Désactiver le compte" #: bookwyrm/templates/moderation/report.html:29 msgid "Reactivate user" -msgstr "" +msgstr "Réactiver le compte" #: bookwyrm/templates/moderation/report.html:36 msgid "Moderator Comments" -msgstr "" +msgstr "Commentaires de l’équipe de modération" #: bookwyrm/templates/moderation/report.html:54 -#: bookwyrm/templates/snippets/create_status.html:12 +#: bookwyrm/templates/snippets/create_status.html:28 #: bookwyrm/templates/snippets/create_status_form.html:44 msgid "Comment" msgstr "Commentaire" #: bookwyrm/templates/moderation/report.html:59 -#, fuzzy #| msgid "Delete status" msgid "Reported statuses" -msgstr "Supprimer le statut" +msgstr "Statuts signalés" #: bookwyrm/templates/moderation/report.html:61 msgid "No statuses reported" -msgstr "" +msgstr "Aucun statut signalé" #: bookwyrm/templates/moderation/report.html:67 msgid "Statuses has been deleted" -msgstr "" +msgstr "Les statuts ont été supprimés" #: bookwyrm/templates/moderation/report_modal.html:6 -#, fuzzy, python-format +#, python-format #| msgid "Join %(name)s" msgid "Report @%(username)s" -msgstr "Listes : %(username)s" +msgstr "Signaler @%(username)s" #: bookwyrm/templates/moderation/report_modal.html:21 #, python-format msgid "This report will be sent to %(site_name)s's moderators for review." -msgstr "" +msgstr "Ce signalement sera envoyé à l’équipe de modération de %(site_name)s pour traitement." #: bookwyrm/templates/moderation/report_modal.html:22 -#, fuzzy #| msgid "More about this site" msgid "More info about this report:" -msgstr "En savoir plus sur ce site" +msgstr "En savoir plus sur ce signalement :" #: bookwyrm/templates/moderation/report_preview.html:13 msgid "No notes provided" -msgstr "" +msgstr "Aucune note fournie" #: bookwyrm/templates/moderation/report_preview.html:20 -#, fuzzy, python-format +#, python-format #| msgid "favorited your %(preview_name)s" msgid "Reported by %(username)s" -msgstr "Messages directs avec %(username)s" +msgstr "Signalé par %(username)s" #: bookwyrm/templates/moderation/report_preview.html:30 msgid "Re-open" -msgstr "" +msgstr "Réouvrir" #: bookwyrm/templates/moderation/report_preview.html:32 msgid "Resolve" -msgstr "" +msgstr "Résoudre" #: bookwyrm/templates/moderation/reports.html:6 -#, fuzzy, python-format +#, python-format #| msgid "Join %(name)s" msgid "Reports: %(server_name)s" -msgstr "Listes : %(username)s" +msgstr "Signalements : %(server_name)s" #: bookwyrm/templates/moderation/reports.html:8 #: bookwyrm/templates/moderation/reports.html:16 #: bookwyrm/templates/settings/admin_layout.html:28 -#, fuzzy #| msgid "Recent Imports" msgid "Reports" -msgstr "Importations récentes" +msgstr "Signalements" #: bookwyrm/templates/moderation/reports.html:13 -#, fuzzy, python-format +#, python-format #| msgid "Join %(name)s" msgid "Reports: %(server_name)s" -msgstr "Listes : %(username)s" +msgstr "Signalements: %(server_name)s" #: bookwyrm/templates/moderation/reports.html:27 -#, fuzzy #| msgid "Shelved" msgid "Resolved" -msgstr "Ajouté à une étagère" +msgstr "Résolus" #: bookwyrm/templates/moderation/reports.html:34 -#, fuzzy #| msgid "No books found" msgid "No reports found." -msgstr "Aucun livre trouvé" +msgstr "Aucun signalement trouvé." #: bookwyrm/templates/notifications.html:14 msgid "Delete notifications" @@ -1663,7 +1623,7 @@ msgstr "Votre importation est terminée." #: bookwyrm/templates/notifications.html:113 #, python-format msgid "A new report needs moderation." -msgstr "" +msgstr "Un nouveau signalement a besoin d’être traité." #: bookwyrm/templates/notifications.html:139 msgid "You're all caught up!" @@ -1710,16 +1670,16 @@ msgstr "Modifier le profil" #: bookwyrm/templates/preferences/edit_user.html:46 msgid "Show set reading goal prompt in feed:" -msgstr "" +msgstr "Afficher le message pour définir un défi lecture dans le fil d’actualité :" #: bookwyrm/templates/preferences/edit_user.html:62 #, python-format msgid "Your account will show up in the directory, and may be recommended to other BookWyrm users." -msgstr "" +msgstr "Votre compte sera listé dans le répertoire et pourra être recommandé à d’autres utilisateurs ou utilisatrices de BookWyrm." #: bookwyrm/templates/preferences/edit_user.html:65 msgid "Preferred Timezone: " -msgstr "" +msgstr "Fuseau horaire préféré" #: bookwyrm/templates/preferences/preferences_layout.html:11 msgid "Account" @@ -1766,7 +1726,7 @@ msgstr "Gérer les comptes" #: bookwyrm/templates/settings/user_admin.html:3 #: bookwyrm/templates/settings/user_admin.html:10 msgid "Users" -msgstr "" +msgstr "Comptes" #: bookwyrm/templates/settings/admin_layout.html:32 #: bookwyrm/templates/settings/federation.html:3 @@ -1781,7 +1741,6 @@ msgstr "Paramètres de l’instance" #: bookwyrm/templates/settings/admin_layout.html:41 #: bookwyrm/templates/settings/site.html:4 #: bookwyrm/templates/settings/site.html:6 -#, fuzzy #| msgid "Instance Settings" msgid "Site Settings" msgstr "Paramètres du site" @@ -1808,29 +1767,26 @@ msgstr "Enregistrement" #: bookwyrm/templates/settings/federated_server.html:7 msgid "Back to server list" -msgstr "" +msgstr "Retour à la liste des serveurs" #: bookwyrm/templates/settings/federated_server.html:12 msgid "Details" -msgstr "" +msgstr "Détails" #: bookwyrm/templates/settings/federated_server.html:15 -#, fuzzy #| msgid "Software" msgid "Software:" -msgstr "Logiciel" +msgstr "Logiciel :" #: bookwyrm/templates/settings/federated_server.html:19 -#, fuzzy #| msgid "Description:" msgid "Version:" msgstr "Description :" #: bookwyrm/templates/settings/federated_server.html:23 -#, fuzzy #| msgid "Status" msgid "Status:" -msgstr "Statut" +msgstr "Statut :" #: bookwyrm/templates/settings/federated_server.html:30 #: bookwyrm/templates/user/user_layout.html:50 @@ -1838,56 +1794,50 @@ msgid "Activity" msgstr "Activité" #: bookwyrm/templates/settings/federated_server.html:33 -#, fuzzy #| msgid "Username:" msgid "Users:" -msgstr "Nom d’utilisateur :" +msgstr "Comptes :" #: bookwyrm/templates/settings/federated_server.html:36 #: bookwyrm/templates/settings/federated_server.html:43 msgid "View all" -msgstr "" +msgstr "Voir tous" #: bookwyrm/templates/settings/federated_server.html:40 -#, fuzzy #| msgid "Recent Imports" msgid "Reports:" -msgstr "Importations récentes" +msgstr "Signalements :" #: bookwyrm/templates/settings/federated_server.html:47 -#, fuzzy #| msgid "followed you" msgid "Followed by us:" -msgstr "s’est abonné(e)" +msgstr "Suivi par nous :" #: bookwyrm/templates/settings/federated_server.html:53 -#, fuzzy #| msgid "followed you" msgid "Followed by them:" -msgstr "s’est abonné(e)" +msgstr "Suivi par eux :" #: bookwyrm/templates/settings/federated_server.html:59 -#, fuzzy #| msgid "Blocked Users" msgid "Blocked by us:" -msgstr "Comptes bloqués" +msgstr "Bloqués par nous :" #: bookwyrm/templates/settings/federation.html:13 msgid "Server name" msgstr "Nom du serveur" #: bookwyrm/templates/settings/federation.html:17 -#, fuzzy #| msgid "Federated" msgid "Date federated" -msgstr "Fédéré" +msgstr "Date de fédération" #: bookwyrm/templates/settings/federation.html:21 msgid "Software" msgstr "Logiciel" #: bookwyrm/templates/settings/federation.html:24 -#: bookwyrm/templates/settings/manage_invite_requests.html:40 +#: bookwyrm/templates/settings/manage_invite_requests.html:44 #: bookwyrm/templates/settings/status_filter.html:5 #: bookwyrm/templates/settings/user_admin.html:32 msgid "Status" @@ -1897,75 +1847,77 @@ msgstr "Statut" #: bookwyrm/templates/settings/manage_invite_requests.html:11 #: bookwyrm/templates/settings/manage_invite_requests.html:25 #: bookwyrm/templates/settings/manage_invites.html:11 -#, fuzzy #| msgid "Invites" msgid "Invite Requests" msgstr "Invitations" #: bookwyrm/templates/settings/manage_invite_requests.html:23 msgid "Ignored Invite Requests" -msgstr "" +msgstr "Invitations ignorées" #: bookwyrm/templates/settings/manage_invite_requests.html:35 -msgid "Date" -msgstr "" +#| msgid "Federated" +msgid "Date requested" +msgstr "Date d’envoi" -#: bookwyrm/templates/settings/manage_invite_requests.html:38 +#: bookwyrm/templates/settings/manage_invite_requests.html:39 +#| msgid "Accept" +msgid "Date accepted" +msgstr "Date de validation" + +#: bookwyrm/templates/settings/manage_invite_requests.html:42 msgid "Email" -msgstr "" +msgstr "Email" -#: bookwyrm/templates/settings/manage_invite_requests.html:43 -#, fuzzy +#: bookwyrm/templates/settings/manage_invite_requests.html:47 #| msgid "Notifications" msgid "Action" -msgstr "Notifications" +msgstr "Action" -#: bookwyrm/templates/settings/manage_invite_requests.html:46 -#, fuzzy +#: bookwyrm/templates/settings/manage_invite_requests.html:50 #| msgid "Follow Requests" msgid "No requests" -msgstr "Demandes d’abonnement" +msgstr "Aucune demande" -#: bookwyrm/templates/settings/manage_invite_requests.html:54 +#: bookwyrm/templates/settings/manage_invite_requests.html:59 #: bookwyrm/templates/settings/status_filter.html:16 -#, fuzzy #| msgid "Accept" msgid "Accepted" -msgstr "Accepter" +msgstr "Accepté(e)s" -#: bookwyrm/templates/settings/manage_invite_requests.html:56 +#: bookwyrm/templates/settings/manage_invite_requests.html:61 #: bookwyrm/templates/settings/status_filter.html:12 msgid "Sent" -msgstr "" +msgstr "Envoyé(e)s" -#: bookwyrm/templates/settings/manage_invite_requests.html:58 +#: bookwyrm/templates/settings/manage_invite_requests.html:63 #: bookwyrm/templates/settings/status_filter.html:8 msgid "Requested" -msgstr "" +msgstr "Demandé(e)s" -#: bookwyrm/templates/settings/manage_invite_requests.html:68 +#: bookwyrm/templates/settings/manage_invite_requests.html:73 msgid "Send invite" -msgstr "" +msgstr "Envoyer l’invitation" -#: bookwyrm/templates/settings/manage_invite_requests.html:70 +#: bookwyrm/templates/settings/manage_invite_requests.html:75 msgid "Re-send invite" -msgstr "" +msgstr "Envoyer l’invitation de nouveau" -#: bookwyrm/templates/settings/manage_invite_requests.html:90 +#: bookwyrm/templates/settings/manage_invite_requests.html:95 msgid "Ignore" -msgstr "" +msgstr "Ignorer" -#: bookwyrm/templates/settings/manage_invite_requests.html:92 +#: bookwyrm/templates/settings/manage_invite_requests.html:97 msgid "Un-ignore" -msgstr "" +msgstr "Ne plus ignorer" -#: bookwyrm/templates/settings/manage_invite_requests.html:103 +#: bookwyrm/templates/settings/manage_invite_requests.html:108 msgid "Back to pending requests" -msgstr "" +msgstr "Retour aux demandes en attente" -#: bookwyrm/templates/settings/manage_invite_requests.html:105 +#: bookwyrm/templates/settings/manage_invite_requests.html:110 msgid "View ignored requests" -msgstr "" +msgstr "Voir les demandes ignorées" #: bookwyrm/templates/settings/manage_invites.html:21 msgid "Generate New Invite" @@ -2052,10 +2004,9 @@ msgid "Allow registration:" msgstr "Autoriser l’enregistrement :" #: bookwyrm/templates/settings/site.html:83 -#, fuzzy #| msgid "Follow Requests" msgid "Allow invite requests:" -msgstr "Demandes d’abonnement" +msgstr "Autoriser les demandes d’invitation :" #: bookwyrm/templates/settings/site.html:87 msgid "Registration closed text:" @@ -2064,43 +2015,39 @@ msgstr "Texte affiché lorsque les enregistrements sont clos :" #: bookwyrm/templates/settings/user_admin.html:7 #, python-format msgid "Users: %(server_name)s" -msgstr "" +msgstr "Comptes : %(server_name)s" #: bookwyrm/templates/settings/user_admin.html:20 -#, fuzzy #| msgid "Username:" msgid "Username" -msgstr "Nom d’utilisateur :" +msgstr "Nom du compte" #: bookwyrm/templates/settings/user_admin.html:24 -#, fuzzy #| msgid "added" msgid "Date Added" -msgstr "a ajouté" +msgstr "Date d’ajout" #: bookwyrm/templates/settings/user_admin.html:28 msgid "Last Active" -msgstr "" +msgstr "Dernière activité" #: bookwyrm/templates/settings/user_admin.html:36 -#, fuzzy #| msgid "Remove" msgid "Remote server" -msgstr "Supprimer" +msgstr "Serveur distant" #: bookwyrm/templates/settings/user_admin.html:45 -#, fuzzy #| msgid "Activity" msgid "Active" -msgstr "Activité" +msgstr "Actif" #: bookwyrm/templates/settings/user_admin.html:45 msgid "Inactive" -msgstr "" +msgstr "Inactif" #: bookwyrm/templates/settings/user_admin.html:50 msgid "Not set" -msgstr "" +msgstr "Non défini" #: bookwyrm/templates/snippets/block_button.html:5 msgid "Block" @@ -2117,8 +2064,8 @@ msgstr "%(title)s par " #: bookwyrm/templates/snippets/boost_button.html:8 #: bookwyrm/templates/snippets/boost_button.html:9 -#: bookwyrm/templates/snippets/status/status_body.html:51 #: bookwyrm/templates/snippets/status/status_body.html:52 +#: bookwyrm/templates/snippets/status/status_body.html:53 msgid "Boost status" msgstr "Partager le statut" @@ -2131,15 +2078,15 @@ msgstr "Annuler le partage du statut" msgid "Spoiler alert:" msgstr "Alerte Spoiler :" -#: bookwyrm/templates/snippets/content_warning_field.html:4 +#: bookwyrm/templates/snippets/content_warning_field.html:10 msgid "Spoilers ahead!" msgstr "Attention spoilers !" -#: bookwyrm/templates/snippets/create_status.html:9 +#: bookwyrm/templates/snippets/create_status.html:17 msgid "Review" msgstr "Critique" -#: bookwyrm/templates/snippets/create_status.html:15 +#: bookwyrm/templates/snippets/create_status.html:39 msgid "Quote" msgstr "Citation" @@ -2148,16 +2095,14 @@ msgid "Comment:" msgstr "Commentaire :" #: bookwyrm/templates/snippets/create_status_form.html:20 -#, fuzzy #| msgid "Quote" msgid "Quote:" -msgstr "Citation" +msgstr "Citation :" #: bookwyrm/templates/snippets/create_status_form.html:22 -#, fuzzy #| msgid "Review" msgid "Review:" -msgstr "Critique" +msgstr "Critique :" #: bookwyrm/templates/snippets/create_status_form.html:29 #: bookwyrm/templates/user/shelf.html:81 @@ -2191,14 +2136,14 @@ msgstr "sur %(pages)s pages" msgid "Include spoiler alert" msgstr "Afficher une alerte spoiler" -#: bookwyrm/templates/snippets/create_status_form.html:87 +#: bookwyrm/templates/snippets/create_status_form.html:88 #: bookwyrm/templates/snippets/privacy-icons.html:15 #: bookwyrm/templates/snippets/privacy-icons.html:16 #: bookwyrm/templates/snippets/privacy_select.html:19 msgid "Private" msgstr "Privé" -#: bookwyrm/templates/snippets/create_status_form.html:94 +#: bookwyrm/templates/snippets/create_status_form.html:99 msgid "Post" msgstr "Publier" @@ -2218,45 +2163,42 @@ msgstr "Supprimer" #: bookwyrm/templates/snippets/fav_button.html:7 #: bookwyrm/templates/snippets/fav_button.html:8 -#: bookwyrm/templates/snippets/status/status_body.html:55 #: bookwyrm/templates/snippets/status/status_body.html:56 +#: bookwyrm/templates/snippets/status/status_body.html:57 msgid "Like status" msgstr "Ajouter le statut aux favoris" #: bookwyrm/templates/snippets/fav_button.html:15 #: bookwyrm/templates/snippets/fav_button.html:16 msgid "Un-like status" -msgstr "Supprimer le statut des favoris" +msgstr "Retirer le statut des favoris" #: bookwyrm/templates/snippets/filters_panel/filters_panel.html:7 -#, fuzzy #| msgid "Show less" msgid "Show filters" -msgstr "Replier" +msgstr "Afficher les filtres" #: bookwyrm/templates/snippets/filters_panel/filters_panel.html:9 msgid "Hide filters" -msgstr "" +msgstr "Masquer les filtres" #: bookwyrm/templates/snippets/filters_panel/filters_panel.html:22 msgid "Apply filters" -msgstr "" +msgstr "Appliquer les filtres" #: bookwyrm/templates/snippets/filters_panel/filters_panel.html:26 -#, fuzzy #| msgid "Clear search" msgid "Clear filters" -msgstr "Vider la requête" +msgstr "Annuler les filtres" #: bookwyrm/templates/snippets/follow_button.html:12 msgid "Follow" msgstr "S’abonner" #: bookwyrm/templates/snippets/follow_button.html:18 -#, fuzzy #| msgid "Send follow request" msgid "Undo follow request" -msgstr "Envoyer une demande d’abonnement" +msgstr "Annuler la demande d’abonnement" #: bookwyrm/templates/snippets/follow_button.html:20 msgid "Unfollow" @@ -2287,29 +2229,29 @@ msgstr[0] "souhaite lire %(counter)s livre en %(year)s" msgstr[1] "souhaite lire %(counter)s livres en %(year)s" #: bookwyrm/templates/snippets/generated_status/rating.html:3 -#, fuzzy, python-format +#, python-format #| msgid "%(title)s by " msgid "Rated %(title)s: %(display_rating)s star" msgid_plural "Rated %(title)s: %(display_rating)s stars" -msgstr[0] "%(title)s par " -msgstr[1] "%(title)s par " +msgstr[0] "A noté %(title)s : %(display_rating)s star" +msgstr[1] "A noté %(title)s : %(display_rating)s stars" #: bookwyrm/templates/snippets/generated_status/review_pure_name.html:4 #, python-format msgid "Review of \"%(book_title)s\" (%(display_rating)s star): %(review_title)s" msgid_plural "Review of \"%(book_title)s\" (%(display_rating)s stars): %(review_title)s" -msgstr[0] "" -msgstr[1] "" +msgstr[0] "Critique de « %(book_title)s » (%(display_rating)s star): %(review_title)s" +msgstr[1] "Critique de « %(book_title)s » (%(display_rating)s stars) : %(review_title)s" #: bookwyrm/templates/snippets/generated_status/review_pure_name.html:8 #, python-format msgid "Review of \"%(book_title)s\": %(review_title)s" -msgstr "" +msgstr "Critique de « %(book_title)s » : %(review_title)s" #: bookwyrm/templates/snippets/goal_card.html:23 #, python-format msgid "You can set or change your reading goal any time from your profile page" -msgstr "Vous pouvez définir ou changer vore défi lecture à n’importe quel moment depuis votre profil" +msgstr "Vous pouvez définir ou changer votre défi lecture à n’importe quel moment depuis votre profil" #: bookwyrm/templates/snippets/goal_form.html:9 msgid "Reading goal:" @@ -2354,22 +2296,22 @@ msgid "%(username)s has read %(read_count)s of %(goal_count msgstr "%(username)s a lu %(read_count)s sur %(goal_count)s livres." #: bookwyrm/templates/snippets/page_text.html:4 -#, fuzzy, python-format +#, python-format #| msgid "of %(pages)s pages" msgid "page %(page)s of %(total_pages)s" -msgstr "sur %(pages)s pages" +msgstr "page %(page)s sur %(total_pages)s pages" #: bookwyrm/templates/snippets/page_text.html:6 -#, fuzzy, python-format +#, python-format #| msgid "of %(book.pages)s pages" msgid "page %(page)s" -msgstr "sur %(book.pages)s pages" +msgstr "page %(page)s" -#: bookwyrm/templates/snippets/pagination.html:5 +#: bookwyrm/templates/snippets/pagination.html:12 msgid "Previous" msgstr "Précédente" -#: bookwyrm/templates/snippets/pagination.html:9 +#: bookwyrm/templates/snippets/pagination.html:23 msgid "Next" msgstr "Suivante" @@ -2432,7 +2374,6 @@ msgid "Edit read dates" msgstr "Modifier les date de lecture" #: bookwyrm/templates/snippets/readthrough.html:61 -#, fuzzy #| msgid "Delete these read dates?" msgid "Delete these read dates" msgstr "Supprimer ces dates de lecture" @@ -2457,10 +2398,9 @@ msgid "Sign Up" msgstr "S’enregistrer" #: bookwyrm/templates/snippets/report_button.html:5 -#, fuzzy #| msgid "Import" msgid "Report" -msgstr "Importer" +msgstr "Signaler" #: bookwyrm/templates/snippets/rss_title.html:5 #: bookwyrm/templates/snippets/status/status_header.html:11 @@ -2483,10 +2423,9 @@ msgid "quoted" msgstr "a cité" #: bookwyrm/templates/snippets/search_result_text.html:10 -#, fuzzy #| msgid "Add cover" msgid "No cover" -msgstr "Ajouter une couverture" +msgstr "Aucune couverture" #: bookwyrm/templates/snippets/search_result_text.html:22 #, python-format @@ -2498,10 +2437,9 @@ msgid "Import book" msgstr "Importer le livre" #: bookwyrm/templates/snippets/shelf_selector.html:4 -#, fuzzy #| msgid "Your books" msgid "Move book" -msgstr "Vos livres" +msgstr "Déplacer le livre" #: bookwyrm/templates/snippets/shelve_button/finish_reading_modal.html:5 #, python-format @@ -2510,23 +2448,20 @@ msgstr "Terminer « %(book_title)s »" #: bookwyrm/templates/snippets/shelve_button/progress_update_modal.html:5 #: bookwyrm/templates/snippets/shelve_button/shelve_button_options.html:35 -#, fuzzy #| msgid "Updated:" msgid "Update progress" -msgstr "Mises à jour" +msgstr "Progression de la mise à jour" #: bookwyrm/templates/snippets/shelve_button/shelve_button_dropdown.html:5 msgid "More shelves" msgstr "Plus d’étagères" #: bookwyrm/templates/snippets/shelve_button/shelve_button_options.html:8 -#, fuzzy #| msgid "Started reading" msgid "Start reading" msgstr "Commencer la lecture" #: bookwyrm/templates/snippets/shelve_button/shelve_button_options.html:13 -#, fuzzy #| msgid "Finished reading" msgid "Finish reading" msgstr "Terminer la lecture" @@ -2537,10 +2472,10 @@ msgid "Want to read" msgstr "Je veux le lire" #: bookwyrm/templates/snippets/shelve_button/shelve_button_options.html:48 -#, fuzzy, python-format +#, python-format #| msgid "Join %(name)s" msgid "Remove from %(name)s" -msgstr "Listes : %(username)s" +msgstr "Retirer de %(name)s" #: bookwyrm/templates/snippets/shelve_button/start_reading_modal.html:5 #, python-format @@ -2561,9 +2496,9 @@ msgstr "partagé" msgid "Delete status" msgstr "Supprimer le statut" -#: bookwyrm/templates/snippets/status/status_body.html:34 -#: bookwyrm/templates/snippets/status/status_body.html:47 +#: bookwyrm/templates/snippets/status/status_body.html:35 #: bookwyrm/templates/snippets/status/status_body.html:48 +#: bookwyrm/templates/snippets/status/status_body.html:49 msgid "Reply" msgstr "Répondre" @@ -2582,49 +2517,52 @@ msgid "Open image in new window" msgstr "Ouvrir l’image dans une nouvelle fenêtre" #: bookwyrm/templates/snippets/status/status_header.html:22 -#, fuzzy, python-format +#, python-format #| msgid "favorited your %(preview_name)s" msgid "replied to %(username)s's review" -msgstr "Messages directs avec %(username)s" +msgstr "a répondu à la critique de %(username)s" #: bookwyrm/templates/snippets/status/status_header.html:24 -#, fuzzy, python-format +#, python-format #| msgid "replied to your status" msgid "replied to %(username)s's comment" -msgstr "a répondu à votre statut" +msgstr "a répondu au commentaire de %(username)s" #: bookwyrm/templates/snippets/status/status_header.html:26 -#, fuzzy, python-format +#, python-format #| msgid "replied to your status" msgid "replied to %(username)s's quote" -msgstr "a répondu à votre statut" +msgstr "a répondu à la citation de %(username)s" #: bookwyrm/templates/snippets/status/status_header.html:28 -#, fuzzy, python-format +#, python-format #| msgid "replied to your status" msgid "replied to %(username)s's status" -msgstr "a répondu à votre statut" +msgstr "a répondu au statut de %(username)s" #: bookwyrm/templates/snippets/status/status_options.html:7 #: bookwyrm/templates/snippets/user_options.html:7 msgid "More options" msgstr "Plus d’options" +#: bookwyrm/templates/snippets/status/status_options.html:27 +#| msgid "Delete these read dates?" +msgid "Delete & re-draft" +msgstr "Supprimer & recommencer la rédaction" + #: bookwyrm/templates/snippets/switch_edition_button.html:5 msgid "Switch to this edition" msgstr "Changer vers cette édition" #: bookwyrm/templates/snippets/table-sort-header.html:6 -#, fuzzy #| msgid "Started reading" -msgid "Sorted asccending" -msgstr "Lecture commencée le" +msgid "Sorted ascending" +msgstr "Trié par ordre croissant" #: bookwyrm/templates/snippets/table-sort-header.html:10 -#, fuzzy #| msgid "Started reading" msgid "Sorted descending" -msgstr "Lecture commencée le" +msgstr "Trié par ordre décroissant" #: bookwyrm/templates/snippets/tag.html:14 msgid "Remove tag" @@ -2640,10 +2578,10 @@ msgid "Books tagged \"%(tag.name)s\"" msgstr "Livres tagués « %(tag.name)s »" #: bookwyrm/templates/user/books_header.html:5 -#, fuzzy, python-format +#, python-format #| msgid "%(username)s has no followers" msgid "%(username)s's books" -msgstr "Livres de %(username)s en %(year)s" +msgstr "Livres de %(username)s" #: bookwyrm/templates/user/create_shelf_form.html:5 #: bookwyrm/templates/user/create_shelf_form.html:22 @@ -2683,7 +2621,7 @@ msgid "Your Lists" msgstr "Vos listes" #: bookwyrm/templates/user/lists.html:11 -#, fuzzy, python-format +#, python-format #| msgid "Join %(name)s" msgid "Lists: %(username)s" msgstr "Listes : %(username)s" @@ -2693,10 +2631,9 @@ msgid "Create list" msgstr "Créer une liste" #: bookwyrm/templates/user/shelf.html:24 bookwyrm/views/shelf.py:56 -#, fuzzy #| msgid "books" msgid "All books" -msgstr "livres" +msgstr "Tous les livres" #: bookwyrm/templates/user/shelf.html:37 msgid "Create shelf" @@ -2731,14 +2668,14 @@ msgid "Edit profile" msgstr "Modifier le profil" #: bookwyrm/templates/user/user.html:34 -#, fuzzy, python-format +#, python-format #| msgid "See all %(size)s" msgid "View all %(size)s" msgstr "Voir les %(size)s" #: bookwyrm/templates/user/user.html:47 msgid "View all books" -msgstr "" +msgstr "Voir tous les livres" #: bookwyrm/templates/user/user.html:59 #, python-format @@ -2766,10 +2703,9 @@ msgid "Reading Goal" msgstr "Défi lecture" #: bookwyrm/templates/user/user_layout.html:68 -#, fuzzy #| msgid "Book" msgid "Books" -msgstr "Livre" +msgstr "Livres" #: bookwyrm/templates/user/user_preview.html:13 #, python-format @@ -2777,12 +2713,12 @@ msgid "Joined %(date)s" msgstr "Enregistré(e) %(date)s" #: bookwyrm/templates/user/user_preview.html:15 -#, fuzzy, python-format +#, python-format #| msgid "%(username)s has no followers" msgid "%(counter)s follower" msgid_plural "%(counter)s followers" -msgstr[0] "%(username)s n’a pas d’abonné(e)" -msgstr[1] "%(username)s n’a pas d’abonné(e)s" +msgstr[0] "%(counter)s abonnement" +msgstr[1] "%(counter)s abonnements" #: bookwyrm/templates/user/user_preview.html:16 #, python-format @@ -2791,12 +2727,12 @@ msgstr "%(counter)s abonnements" #: bookwyrm/views/password.py:32 msgid "No user with that email address was found." -msgstr "" +msgstr "Aucun compte avec cette adresse email n’a été trouvé." #: bookwyrm/views/password.py:41 #, python-format msgid "A password reset link sent to %s" -msgstr "" +msgstr "Un lien de réinitialisation a été envoyé à %s." #, fuzzy #~| msgid "Started" diff --git a/locale/zh_CN/LC_MESSAGES/django.mo b/locale/zh_CN/LC_MESSAGES/django.mo index 019000e2a74794ac764fb9313da0e56c4fa59479..07b8d63ca2e7a940d4feb0d0a94892b7a708e8b5 100644 GIT binary patch delta 5717 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 delta 5717 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 Date: Mon, 5 Apr 2021 12:53:20 +0200 Subject: [PATCH 02/14] [l10n] Add missing i18n string to template. --- bookwyrm/templates/lists/lists.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bookwyrm/templates/lists/lists.html b/bookwyrm/templates/lists/lists.html index a92583054..27e56f11a 100644 --- a/bookwyrm/templates/lists/lists.html +++ b/bookwyrm/templates/lists/lists.html @@ -11,7 +11,7 @@

{% trans "Lists" %} {% if request.user.is_authenticated %} - Your lists + {% trans "Your Lists" %} {% endif %}

From 21ba05b4a5fd8862bb17f6283f50f441efc87e14 Mon Sep 17 00:00:00 2001 From: Your Name Date: Mon, 5 Apr 2021 18:29:01 +0200 Subject: [PATCH 03/14] Added wyrms.de --- instances.md | 1 + 1 file changed, 1 insertion(+) diff --git a/instances.md b/instances.md index 1c8b282b4..570328b51 100644 --- a/instances.md +++ b/instances.md @@ -2,3 +2,4 @@ | name | url | admin contact | open registration | | :--- | :-- | :------------ | :---------------- | | bookwyrm.social | http://bookwyrm.social/ | mousereeve@riseup.net / @tripofmice@friend.camp | ❌ | +| wyrms.de | https://wyrms.de/ | wyrms@tofuwabo.hu / @tofuwabohu@subversive.zone | ❌ | From 04f459a2dff559d8e7edcd2cf96c684fff1eecd2 Mon Sep 17 00:00:00 2001 From: Mouse Reeve Date: Mon, 5 Apr 2021 10:17:01 -0700 Subject: [PATCH 04/14] Fixes creting invites --- bookwyrm/forms.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bookwyrm/forms.py b/bookwyrm/forms.py index 1a114e05f..b159a89ef 100644 --- a/bookwyrm/forms.py +++ b/bookwyrm/forms.py @@ -233,7 +233,7 @@ class InviteRequestForm(CustomForm): class CreateInviteForm(CustomForm): class Meta: model = models.SiteInvite - exclude = ["code", "user", "times_used"] + exclude = ["code", "user", "times_used", "invitees"] widgets = { "expiry": ExpiryWidget( choices=[ From bd8858830ac89c497235f2389bf0f8ea8b63afee Mon Sep 17 00:00:00 2001 From: Mouse Reeve Date: Mon, 5 Apr 2021 11:05:37 -0700 Subject: [PATCH 05/14] Created generic redis class for activitstreams --- bookwyrm/activitystreams.py | 114 +++++++++++++----------------------- bookwyrm/redis_store.py | 91 ++++++++++++++++++++++++++++ bookwyrm/views/feed.py | 1 - 3 files changed, 133 insertions(+), 73 deletions(-) create mode 100644 bookwyrm/redis_store.py diff --git a/bookwyrm/activitystreams.py b/bookwyrm/activitystreams.py index 279079c81..8d1e83018 100644 --- a/bookwyrm/activitystreams.py +++ b/bookwyrm/activitystreams.py @@ -1,18 +1,13 @@ """ access the activity streams stored in redis """ -from abc import ABC from django.dispatch import receiver from django.db.models import signals, Q -import redis -from bookwyrm import models, settings +from bookwyrm import models +from bookwyrm.redis_store import RedisStore, r from bookwyrm.views.helpers import privacy_filter -r = redis.Redis( - host=settings.REDIS_ACTIVITY_HOST, port=settings.REDIS_ACTIVITY_PORT, db=0 -) - -class ActivityStream(ABC): +class ActivityStream(RedisStore): """ a category of activity stream (like home, local, federated) """ def stream_id(self, user): @@ -23,58 +18,38 @@ class ActivityStream(ABC): """ the redis key for this user's unread count for this stream """ return "{}-unread".format(self.stream_id(user)) - def get_value(self, status): # pylint: disable=no-self-use - """ the status id and the rank (ie, published date) """ - return {status.id: status.published_date.timestamp()} + def get_rank(self, obj): # pylint: disable=no-self-use + """ the sort rank of a status, which is published date """ + return obj.published_date.timestamp() def add_status(self, status): """ add a status to users' feeds """ - value = self.get_value(status) - # we want to do this as a bulk operation, hence "pipeline" - pipeline = r.pipeline() - for user in self.stream_users(status): - # add the status to the feed - pipeline.zadd(self.stream_id(user), value) - pipeline.zremrangebyrank( - self.stream_id(user), 0, -1 * settings.MAX_STREAM_LENGTH - ) + # the pipeline contains all the addp-to-stream activities + pipeline = self.add_object_to_related_stores(status, execute=False) + + for user in self.get_audience(status): # add to the unread status count pipeline.incr(self.unread_id(user)) - # and go! - pipeline.execute() - def remove_status(self, status): - """ remove a status from all feeds """ - pipeline = r.pipeline() - for user in self.stream_users(status): - pipeline.zrem(self.stream_id(user), -1, status.id) + # and go! pipeline.execute() def add_user_statuses(self, viewer, user): """ add a user's statuses to another user's feed """ - pipeline = r.pipeline() - statuses = user.status_set.all()[: settings.MAX_STREAM_LENGTH] - for status in statuses: - pipeline.zadd(self.stream_id(viewer), self.get_value(status)) - if statuses: - pipeline.zremrangebyrank( - self.stream_id(user), 0, -1 * settings.MAX_STREAM_LENGTH - ) - pipeline.execute() + statuses = privacy_filter(viewer, user.status_set.all()) + self.bulk_add_objects_to_store(statuses, self.stream_id(viewer)) def remove_user_statuses(self, viewer, user): """ remove a user's status from another user's feed """ - pipeline = r.pipeline() - for status in user.status_set.all()[: settings.MAX_STREAM_LENGTH]: - pipeline.lrem(self.stream_id(viewer), -1, status.id) - pipeline.execute() + statuses = user.status_set.all() + self.bulk_remove_objects_from_store(statuses, self.stream_id(viewer)) def get_activity_stream(self, user): - """ load the ids for statuses to be displayed """ + """ load the statuses to be displayed """ # clear unreads for this feed r.set(self.unread_id(user), 0) - statuses = r.zrevrange(self.stream_id(user), 0, -1) + statuses = super().get_store(self.stream_id(user)) return ( models.Status.objects.select_subclasses() .filter(id__in=statuses) @@ -85,23 +60,11 @@ class ActivityStream(ABC): """ get the unread status count for this user's feed """ return int(r.get(self.unread_id(user)) or 0) - def populate_stream(self, user): + def populate_streamse(self, user): """ go from zero to a timeline """ - pipeline = r.pipeline() - statuses = self.stream_statuses(user) + super().populate_store(self.stream_id(user)) - stream_id = self.stream_id(user) - for status in statuses.all()[: settings.MAX_STREAM_LENGTH]: - pipeline.zadd(stream_id, self.get_value(status)) - - # only trim the stream if statuses were added - if statuses.exists(): - pipeline.zremrangebyrank( - self.stream_id(user), 0, -1 * settings.MAX_STREAM_LENGTH - ) - pipeline.execute() - - def stream_users(self, status): # pylint: disable=no-self-use + def get_audience(self, status): # pylint: disable=no-self-use """ given a status, what users should see it """ # direct messages don't appeard in feeds, direct comments/reviews/etc do if status.privacy == "direct" and status.status_type == "Note": @@ -129,7 +92,10 @@ class ActivityStream(ABC): ) return audience.distinct() - def stream_statuses(self, user): # pylint: disable=no-self-use + def get_stores_for_object(self, obj): + return [self.stream_id(u) for u in self.get_audience(obj)] + + def get_statuses_for_user(self, user): # pylint: disable=no-self-use """ given a user, what statuses should they see on this stream """ return privacy_filter( user, @@ -137,14 +103,18 @@ class ActivityStream(ABC): privacy_levels=["public", "unlisted", "followers"], ) + def get_objects_for_store(self, store): + user = models.User.objects.get(id=store.split('-')[0]) + return self.get_statuses_for_user(user) + class HomeStream(ActivityStream): """ users you follow """ key = "home" - def stream_users(self, status): - audience = super().stream_users(status) + def get_audience(self, status): + audience = super().get_audience(status) if not audience: return [] return audience.filter( @@ -152,7 +122,7 @@ class HomeStream(ActivityStream): | Q(following=status.user) # if the user is following the author ).distinct() - def stream_statuses(self, user): + def get_statuses_for_user(self, user): return privacy_filter( user, models.Status.objects.select_subclasses(), @@ -166,13 +136,13 @@ class LocalStream(ActivityStream): key = "local" - def stream_users(self, status): + def get_audience(self, status): # this stream wants no part in non-public statuses if status.privacy != "public" or not status.user.local: return [] - return super().stream_users(status) + return super().get_audience(status) - def stream_statuses(self, user): + def get_statuses_for_user(self, user): # all public statuses by a local user return privacy_filter( user, @@ -186,13 +156,13 @@ class FederatedStream(ActivityStream): key = "federated" - def stream_users(self, status): + def get_audience(self, status): # this stream wants no part in non-public statuses if status.privacy != "public": return [] - return super().stream_users(status) + return super().get_audience(status) - def stream_statuses(self, user): + def get_statuses_for_user(self, user): return privacy_filter( user, models.Status.objects.select_subclasses(), @@ -217,7 +187,7 @@ def add_status_on_create(sender, instance, created, *args, **kwargs): if instance.deleted: for stream in streams.values(): - stream.remove_status(instance) + stream.remove_object_from_related_stores(instance) return if not created: @@ -234,7 +204,7 @@ def remove_boost_on_delete(sender, instance, *args, **kwargs): """ boosts are deleted """ # we're only interested in new statuses for stream in streams.values(): - stream.remove_status(instance) + stream.remove_object_from_related_stores(instance) @receiver(signals.post_save, sender=models.UserFollows) @@ -248,7 +218,7 @@ def add_statuses_on_follow(sender, instance, created, *args, **kwargs): @receiver(signals.post_delete, sender=models.UserFollows) # pylint: disable=unused-argument -def remove_statuses_on_unfollow(sender, instance, *args, **kwargs): +def remove_objectes_on_unfollow(sender, instance, *args, **kwargs): """ remove statuses from a feed on unfollow """ if not instance.user_subject.local: return @@ -257,7 +227,7 @@ def remove_statuses_on_unfollow(sender, instance, *args, **kwargs): @receiver(signals.post_save, sender=models.UserBlocks) # pylint: disable=unused-argument -def remove_statuses_on_block(sender, instance, *args, **kwargs): +def remove_objectes_on_block(sender, instance, *args, **kwargs): """ remove statuses from all feeds on block """ # blocks apply ot all feeds if instance.user_subject.local: @@ -294,4 +264,4 @@ def populate_streams_on_account_create(sender, instance, created, *args, **kwarg return for stream in streams.values(): - stream.populate_stream(instance) + stream.populate_streams(instance) diff --git a/bookwyrm/redis_store.py b/bookwyrm/redis_store.py new file mode 100644 index 000000000..8255fcbda --- /dev/null +++ b/bookwyrm/redis_store.py @@ -0,0 +1,91 @@ +""" access the activity stores stored in redis """ +from abc import ABC, abstractmethod +import redis + +from bookwyrm import settings + +r = redis.Redis( + host=settings.REDIS_ACTIVITY_HOST, port=settings.REDIS_ACTIVITY_PORT, db=0 +) + + +class RedisStore(ABC): + """ sets of ranked, related objects, like statuses for a user's feed """ + max_length = settings.MAX_STREAM_LENGTH + + def get_value(self, obj): + """ the object and rank """ + return {obj.id: self.get_rank(obj)} + + def add_object_to_related_stores(self, obj, execute=True): + """ add an object to all suitable stores """ + value = self.get_value(obj) + # we want to do this as a bulk operation, hence "pipeline" + pipeline = r.pipeline() + for store in self.get_stores_for_object(obj): + # add the status to the feed + pipeline.zadd(store, value) + # trim the store + pipeline.zremrangebyrank( + store, 0, -1 * self.max_length + ) + if not execute: + return pipeline + # and go! + return pipeline.execute() + + def remove_object_from_related_stores(self, obj): + """ remove an object from all stores """ + pipeline = r.pipeline() + for store in self.get_stores_for_object(obj): + pipeline.zrem(store, -1, obj.id) + pipeline.execute() + + def bulk_add_objects_to_store(self, objs, store): + """ add a list of objects to a given store """ + pipeline = r.pipeline() + for obj in objs[:self.max_length]: + pipeline.zadd(store, self.get_value(obj)) + if objs: + pipeline.zremrangebyrank( + store, 0, -1 * self.max_length + ) + pipeline.execute() + + def bulk_remove_objects_from_store(self, objs, store): + """ remoev a list of objects from a given store """ + pipeline = r.pipeline() + for obj in objs[:self.max_length]: + pipeline.zrem(store, -1, obj.id) + pipeline.execute() + + def get_store(self, store): # pylint: disable=no-self-use + """ load the values in a store """ + return r.zrevrange(store, 0, -1) + + def populate_store(self, store): + """ go from zero to a store """ + pipeline = r.pipeline() + queryset = self.get_objects_for_store(store) + + for obj in queryset[:self.max_length]: + pipeline.zadd(store, self.get_value(obj)) + + # only trim the store if objects were added + if queryset.exists(): + pipeline.zremrangebyrank( + store, 0, -1 * self.max_length + ) + pipeline.execute() + + @abstractmethod + def get_objects_for_store(self, store): + """ a queryset of what should go in a store, used for populating it """ + + @abstractmethod + def get_stores_for_object(self, obj): + """ the stores that an object belongs in """ + + @abstractmethod + def get_rank(self, obj): + """ how to rank an object """ diff --git a/bookwyrm/views/feed.py b/bookwyrm/views/feed.py index e4be50e33..cda115867 100644 --- a/bookwyrm/views/feed.py +++ b/bookwyrm/views/feed.py @@ -31,7 +31,6 @@ class Feed(View): tab = "home" activities = activitystreams.streams[tab].get_activity_stream(request.user) - paginated = Paginator(activities, PAGE_LENGTH) suggested_users = get_suggested_users(request.user) From 0bbaf0a562b9e2f3521a1e096ade9f509659fb8f Mon Sep 17 00:00:00 2001 From: Mouse Reeve Date: Mon, 5 Apr 2021 11:10:26 -0700 Subject: [PATCH 06/14] Python formatting --- bookwyrm/activitystreams.py | 2 +- bookwyrm/redis_store.py | 19 +++++++------------ 2 files changed, 8 insertions(+), 13 deletions(-) diff --git a/bookwyrm/activitystreams.py b/bookwyrm/activitystreams.py index 8d1e83018..3a815322b 100644 --- a/bookwyrm/activitystreams.py +++ b/bookwyrm/activitystreams.py @@ -104,7 +104,7 @@ class ActivityStream(RedisStore): ) def get_objects_for_store(self, store): - user = models.User.objects.get(id=store.split('-')[0]) + user = models.User.objects.get(id=store.split("-")[0]) return self.get_statuses_for_user(user) diff --git a/bookwyrm/redis_store.py b/bookwyrm/redis_store.py index 8255fcbda..4236d6df2 100644 --- a/bookwyrm/redis_store.py +++ b/bookwyrm/redis_store.py @@ -11,6 +11,7 @@ r = redis.Redis( class RedisStore(ABC): """ sets of ranked, related objects, like statuses for a user's feed """ + max_length = settings.MAX_STREAM_LENGTH def get_value(self, obj): @@ -26,9 +27,7 @@ class RedisStore(ABC): # add the status to the feed pipeline.zadd(store, value) # trim the store - pipeline.zremrangebyrank( - store, 0, -1 * self.max_length - ) + pipeline.zremrangebyrank(store, 0, -1 * self.max_length) if not execute: return pipeline # and go! @@ -44,18 +43,16 @@ class RedisStore(ABC): def bulk_add_objects_to_store(self, objs, store): """ add a list of objects to a given store """ pipeline = r.pipeline() - for obj in objs[:self.max_length]: + for obj in objs[: self.max_length]: pipeline.zadd(store, self.get_value(obj)) if objs: - pipeline.zremrangebyrank( - store, 0, -1 * self.max_length - ) + pipeline.zremrangebyrank(store, 0, -1 * self.max_length) pipeline.execute() def bulk_remove_objects_from_store(self, objs, store): """ remoev a list of objects from a given store """ pipeline = r.pipeline() - for obj in objs[:self.max_length]: + for obj in objs[: self.max_length]: pipeline.zrem(store, -1, obj.id) pipeline.execute() @@ -68,14 +65,12 @@ class RedisStore(ABC): pipeline = r.pipeline() queryset = self.get_objects_for_store(store) - for obj in queryset[:self.max_length]: + for obj in queryset[: self.max_length]: pipeline.zadd(store, self.get_value(obj)) # only trim the store if objects were added if queryset.exists(): - pipeline.zremrangebyrank( - store, 0, -1 * self.max_length - ) + pipeline.zremrangebyrank(store, 0, -1 * self.max_length) pipeline.execute() @abstractmethod From cd56abcb0833986885ec063585f7203eaaa00b21 Mon Sep 17 00:00:00 2001 From: Mouse Reeve Date: Mon, 5 Apr 2021 12:11:49 -0700 Subject: [PATCH 07/14] Maintain signal names --- bookwyrm/activitystreams.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/bookwyrm/activitystreams.py b/bookwyrm/activitystreams.py index 3a815322b..fc50ef7b6 100644 --- a/bookwyrm/activitystreams.py +++ b/bookwyrm/activitystreams.py @@ -218,7 +218,7 @@ def add_statuses_on_follow(sender, instance, created, *args, **kwargs): @receiver(signals.post_delete, sender=models.UserFollows) # pylint: disable=unused-argument -def remove_objectes_on_unfollow(sender, instance, *args, **kwargs): +def remove_statuses_on_unfollow(sender, instance, *args, **kwargs): """ remove statuses from a feed on unfollow """ if not instance.user_subject.local: return @@ -227,7 +227,7 @@ def remove_objectes_on_unfollow(sender, instance, *args, **kwargs): @receiver(signals.post_save, sender=models.UserBlocks) # pylint: disable=unused-argument -def remove_objectes_on_block(sender, instance, *args, **kwargs): +def remove_statuses_on_block(sender, instance, *args, **kwargs): """ remove statuses from all feeds on block """ # blocks apply ot all feeds if instance.user_subject.local: From 04b9704187d4a46885cc5a09baf31165282ea1da Mon Sep 17 00:00:00 2001 From: Mouse Reeve Date: Mon, 5 Apr 2021 13:13:56 -0700 Subject: [PATCH 08/14] typo fix --- bookwyrm/activitystreams.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bookwyrm/activitystreams.py b/bookwyrm/activitystreams.py index fc50ef7b6..2db6cc20a 100644 --- a/bookwyrm/activitystreams.py +++ b/bookwyrm/activitystreams.py @@ -60,7 +60,7 @@ class ActivityStream(RedisStore): """ get the unread status count for this user's feed """ return int(r.get(self.unread_id(user)) or 0) - def populate_streamse(self, user): + def populate_streams(self, user): """ go from zero to a timeline """ super().populate_store(self.stream_id(user)) From 56330d448b12d7a029b51019e5766050a7b476bb Mon Sep 17 00:00:00 2001 From: Mouse Reeve Date: Mon, 5 Apr 2021 14:08:24 -0700 Subject: [PATCH 09/14] Changes remove status redis mock --- bookwyrm/tests/models/test_status_model.py | 4 +++- bookwyrm/tests/test_templatetags.py | 4 +++- bookwyrm/tests/views/test_inbox.py | 6 +++--- bookwyrm/tests/views/test_interaction.py | 2 +- bookwyrm/tests/views/test_status.py | 16 +++++++++++----- 5 files changed, 21 insertions(+), 11 deletions(-) diff --git a/bookwyrm/tests/models/test_status_model.py b/bookwyrm/tests/models/test_status_model.py index 208bf3ab4..4263c4572 100644 --- a/bookwyrm/tests/models/test_status_model.py +++ b/bookwyrm/tests/models/test_status_model.py @@ -116,7 +116,9 @@ class Status(TestCase): def test_status_to_activity_tombstone(self, *_): """ subclass of the base model version with a "pure" serializer """ - with patch("bookwyrm.activitystreams.ActivityStream.remove_status"): + with patch( + "bookwyrm.activitystreams.ActivityStream.remove_object_from_related_stores" + ): status = models.Status.objects.create( content="test content", user=self.local_user, diff --git a/bookwyrm/tests/test_templatetags.py b/bookwyrm/tests/test_templatetags.py index 61136c2eb..b4dc517f1 100644 --- a/bookwyrm/tests/test_templatetags.py +++ b/bookwyrm/tests/test_templatetags.py @@ -85,7 +85,9 @@ class TemplateTags(TestCase): second_child = models.Status.objects.create( reply_parent=parent, user=self.user, content="hi" ) - with patch("bookwyrm.activitystreams.ActivityStream.remove_status"): + with patch( + "bookwyrm.activitystreams.ActivityStream.remove_object_from_related_stores" + ): third_child = models.Status.objects.create( reply_parent=parent, user=self.user, diff --git a/bookwyrm/tests/views/test_inbox.py b/bookwyrm/tests/views/test_inbox.py index 37cf00ddc..935909e10 100644 --- a/bookwyrm/tests/views/test_inbox.py +++ b/bookwyrm/tests/views/test_inbox.py @@ -444,7 +444,7 @@ class Inbox(TestCase): "object": {"id": self.status.remote_id, "type": "Tombstone"}, } with patch( - "bookwyrm.activitystreams.ActivityStream.remove_status" + "bookwyrm.activitystreams.ActivityStream.remove_object_from_related_stores" ) as redis_mock: views.inbox.activity_task(activity) self.assertTrue(redis_mock.called) @@ -477,7 +477,7 @@ class Inbox(TestCase): "object": {"id": self.status.remote_id, "type": "Tombstone"}, } with patch( - "bookwyrm.activitystreams.ActivityStream.remove_status" + "bookwyrm.activitystreams.ActivityStream.remove_object_from_related_stores" ) as redis_mock: views.inbox.activity_task(activity) self.assertTrue(redis_mock.called) @@ -616,7 +616,7 @@ class Inbox(TestCase): }, } with patch( - "bookwyrm.activitystreams.ActivityStream.remove_status" + "bookwyrm.activitystreams.ActivityStream.remove_object_from_related_stores" ) as redis_mock: views.inbox.activity_task(activity) self.assertTrue(redis_mock.called) diff --git a/bookwyrm/tests/views/test_interaction.py b/bookwyrm/tests/views/test_interaction.py index 297eeb73d..8d2c63ffc 100644 --- a/bookwyrm/tests/views/test_interaction.py +++ b/bookwyrm/tests/views/test_interaction.py @@ -164,7 +164,7 @@ class InteractionViews(TestCase): self.assertEqual(models.Boost.objects.count(), 1) self.assertEqual(models.Notification.objects.count(), 1) with patch( - "bookwyrm.activitystreams.ActivityStream.remove_status" + "bookwyrm.activitystreams.ActivityStream.remove_object_from_related_stores" ) as redis_mock: view(request, status.id) self.assertTrue(redis_mock.called) diff --git a/bookwyrm/tests/views/test_status.py b/bookwyrm/tests/views/test_status.py index e7fc62d56..5eb13b6b2 100644 --- a/bookwyrm/tests/views/test_status.py +++ b/bookwyrm/tests/views/test_status.py @@ -177,7 +177,9 @@ class StatusViews(TestCase): content="hi", book=self.book, user=self.local_user ) - with patch("bookwyrm.activitystreams.ActivityStream.remove_status") as mock: + with patch( + "bookwyrm.activitystreams.ActivityStream.remove_object_from_related_stores" + ) as mock: result = view(request, status.id) self.assertTrue(mock.called) result.render() @@ -196,7 +198,9 @@ class StatusViews(TestCase): book=self.book, rating=2.0, user=self.local_user ) - with patch("bookwyrm.activitystreams.ActivityStream.remove_status") as mock: + with patch( + "bookwyrm.activitystreams.ActivityStream.remove_object_from_related_stores" + ) as mock: result = view(request, status.id) self.assertFalse(mock.called) self.assertEqual(result.status_code, 400) @@ -214,7 +218,9 @@ class StatusViews(TestCase): content="hi", user=self.local_user ) - with patch("bookwyrm.activitystreams.ActivityStream.remove_status") as mock: + with patch( + "bookwyrm.activitystreams.ActivityStream.remove_object_from_related_stores" + ) as mock: result = view(request, status.id) self.assertFalse(mock.called) self.assertEqual(result.status_code, 400) @@ -316,7 +322,7 @@ class StatusViews(TestCase): request.user = self.local_user with patch( - "bookwyrm.activitystreams.ActivityStream.remove_status" + "bookwyrm.activitystreams.ActivityStream.remove_object_from_related_stores" ) as redis_mock: view(request, status.id) self.assertTrue(redis_mock.called) @@ -351,7 +357,7 @@ class StatusViews(TestCase): request.user.is_superuser = True with patch( - "bookwyrm.activitystreams.ActivityStream.remove_status" + "bookwyrm.activitystreams.ActivityStream.remove_object_from_related_stores" ) as redis_mock: view(request, status.id) self.assertTrue(redis_mock.called) From 6a3c01a67f3fc7fd932469bc1bf82377952043be Mon Sep 17 00:00:00 2001 From: Mouse Reeve Date: Mon, 5 Apr 2021 14:17:45 -0700 Subject: [PATCH 10/14] stream_users function has been renamed --- bookwyrm/tests/test_activitystreams.py | 58 +++++++++++++------------- 1 file changed, 29 insertions(+), 29 deletions(-) diff --git a/bookwyrm/tests/test_activitystreams.py b/bookwyrm/tests/test_activitystreams.py index 88ca4693b..b4efeba3f 100644 --- a/bookwyrm/tests/test_activitystreams.py +++ b/bookwyrm/tests/test_activitystreams.py @@ -47,18 +47,18 @@ class Activitystreams(TestCase): "{}-test-unread".format(self.local_user.id), ) - def test_abstractstream_stream_users(self, *_): + def test_abstractstream_get_audience(self, *_): """ get a list of users that should see a status """ status = models.Status.objects.create( user=self.remote_user, content="hi", privacy="public" ) - users = self.test_stream.stream_users(status) + users = self.test_stream.get_audience(status) # remote users don't have feeds self.assertFalse(self.remote_user in users) self.assertTrue(self.local_user in users) self.assertTrue(self.another_user in users) - def test_abstractstream_stream_users_direct(self, *_): + def test_abstractstream_get_audience_direct(self, *_): """ get a list of users that should see a status """ status = models.Status.objects.create( user=self.remote_user, @@ -66,7 +66,7 @@ class Activitystreams(TestCase): privacy="direct", ) status.mention_users.add(self.local_user) - users = self.test_stream.stream_users(status) + users = self.test_stream.get_audience(status) self.assertEqual(users, []) status = models.Comment.objects.create( @@ -76,22 +76,22 @@ class Activitystreams(TestCase): book=self.book, ) status.mention_users.add(self.local_user) - users = self.test_stream.stream_users(status) + users = self.test_stream.get_audience(status) self.assertTrue(self.local_user in users) self.assertFalse(self.another_user in users) self.assertFalse(self.remote_user in users) - def test_abstractstream_stream_users_followers_remote_user(self, *_): + def test_abstractstream_get_audience_followers_remote_user(self, *_): """ get a list of users that should see a status """ status = models.Status.objects.create( user=self.remote_user, content="hi", privacy="followers", ) - users = self.test_stream.stream_users(status) + users = self.test_stream.get_audience(status) self.assertFalse(users.exists()) - def test_abstractstream_stream_users_followers_self(self, *_): + def test_abstractstream_get_audience_followers_self(self, *_): """ get a list of users that should see a status """ status = models.Comment.objects.create( user=self.local_user, @@ -99,12 +99,12 @@ class Activitystreams(TestCase): privacy="direct", book=self.book, ) - users = self.test_stream.stream_users(status) + users = self.test_stream.get_audience(status) self.assertTrue(self.local_user in users) self.assertFalse(self.another_user in users) self.assertFalse(self.remote_user in users) - def test_abstractstream_stream_users_followers_with_mention(self, *_): + def test_abstractstream_get_audience_followers_with_mention(self, *_): """ get a list of users that should see a status """ status = models.Comment.objects.create( user=self.remote_user, @@ -114,12 +114,12 @@ class Activitystreams(TestCase): ) status.mention_users.add(self.local_user) - users = self.test_stream.stream_users(status) + users = self.test_stream.get_audience(status) self.assertTrue(self.local_user in users) self.assertFalse(self.another_user in users) self.assertFalse(self.remote_user in users) - def test_abstractstream_stream_users_followers_with_relationship(self, *_): + def test_abstractstream_get_audience_followers_with_relationship(self, *_): """ get a list of users that should see a status """ self.remote_user.followers.add(self.local_user) status = models.Comment.objects.create( @@ -128,77 +128,77 @@ class Activitystreams(TestCase): privacy="direct", book=self.book, ) - users = self.test_stream.stream_users(status) + users = self.test_stream.get_audience(status) self.assertFalse(self.local_user in users) self.assertFalse(self.another_user in users) self.assertFalse(self.remote_user in users) - def test_homestream_stream_users(self, *_): + def test_homestream_get_audience(self, *_): """ get a list of users that should see a status """ status = models.Status.objects.create( user=self.remote_user, content="hi", privacy="public" ) - users = activitystreams.HomeStream().stream_users(status) + users = activitystreams.HomeStream().get_audience(status) self.assertFalse(users.exists()) - def test_homestream_stream_users_with_mentions(self, *_): + def test_homestream_get_audience_with_mentions(self, *_): """ get a list of users that should see a status """ status = models.Status.objects.create( user=self.remote_user, content="hi", privacy="public" ) status.mention_users.add(self.local_user) - users = activitystreams.HomeStream().stream_users(status) + users = activitystreams.HomeStream().get_audience(status) self.assertFalse(self.local_user in users) self.assertFalse(self.another_user in users) - def test_homestream_stream_users_with_relationship(self, *_): + def test_homestream_get_audience_with_relationship(self, *_): """ get a list of users that should see a status """ self.remote_user.followers.add(self.local_user) status = models.Status.objects.create( user=self.remote_user, content="hi", privacy="public" ) - users = activitystreams.HomeStream().stream_users(status) + users = activitystreams.HomeStream().get_audience(status) self.assertTrue(self.local_user in users) self.assertFalse(self.another_user in users) - def test_localstream_stream_users_remote_status(self, *_): + def test_localstream_get_audience_remote_status(self, *_): """ get a list of users that should see a status """ status = models.Status.objects.create( user=self.remote_user, content="hi", privacy="public" ) - users = activitystreams.LocalStream().stream_users(status) + users = activitystreams.LocalStream().get_audience(status) self.assertEqual(users, []) - def test_localstream_stream_users_local_status(self, *_): + def test_localstream_get_audience_local_status(self, *_): """ get a list of users that should see a status """ status = models.Status.objects.create( user=self.local_user, content="hi", privacy="public" ) - users = activitystreams.LocalStream().stream_users(status) + users = activitystreams.LocalStream().get_audience(status) self.assertTrue(self.local_user in users) self.assertTrue(self.another_user in users) - def test_localstream_stream_users_unlisted(self, *_): + def test_localstream_get_audience_unlisted(self, *_): """ get a list of users that should see a status """ status = models.Status.objects.create( user=self.local_user, content="hi", privacy="unlisted" ) - users = activitystreams.LocalStream().stream_users(status) + users = activitystreams.LocalStream().get_audience(status) self.assertEqual(users, []) - def test_federatedstream_stream_users(self, *_): + def test_federatedstream_get_audience(self, *_): """ get a list of users that should see a status """ status = models.Status.objects.create( user=self.remote_user, content="hi", privacy="public" ) - users = activitystreams.FederatedStream().stream_users(status) + users = activitystreams.FederatedStream().get_audience(status) self.assertTrue(self.local_user in users) self.assertTrue(self.another_user in users) - def test_federatedstream_stream_users_unlisted(self, *_): + def test_federatedstream_get_audience_unlisted(self, *_): """ get a list of users that should see a status """ status = models.Status.objects.create( user=self.remote_user, content="hi", privacy="unlisted" ) - users = activitystreams.FederatedStream().stream_users(status) + users = activitystreams.FederatedStream().get_audience(status) self.assertEqual(users, []) From 2e245f84be846e967a1488aeb7f8b4ffe66408e0 Mon Sep 17 00:00:00 2001 From: Mouse Reeve Date: Mon, 5 Apr 2021 18:02:24 -0700 Subject: [PATCH 11/14] Adds test for loading remote boosted statuses --- bookwyrm/tests/views/test_inbox.py | 50 ++++++++++++++++++++++++++++++ 1 file changed, 50 insertions(+) diff --git a/bookwyrm/tests/views/test_inbox.py b/bookwyrm/tests/views/test_inbox.py index 37cf00ddc..6973f9814 100644 --- a/bookwyrm/tests/views/test_inbox.py +++ b/bookwyrm/tests/views/test_inbox.py @@ -572,6 +572,56 @@ class Inbox(TestCase): self.assertEqual(notification.user, self.local_user) self.assertEqual(notification.related_status, self.status) + @responses.activate + @patch("bookwyrm.activitystreams.ActivityStream.add_status") + def test_handle_boost_remote_status(self, redis_mock): + """ boost a status """ + work = models.Work.objects.create(title="work title") + book = models.Edition.objects.create( + title="Test", + remote_id="https://bookwyrm.social/book/37292", + parent_work=work, + ) + self.assertEqual(models.Notification.objects.count(), 0) + activity = { + "type": "Announce", + "id": "%s/boost" % self.status.remote_id, + "actor": self.remote_user.remote_id, + "object": "https://remote.com/status/1", + "to": ["https://www.w3.org/ns/activitystreams#public"], + "cc": ["https://example.com/user/mouse/followers"], + "published": "Mon, 25 May 2020 19:31:20 GMT", + } + responses.add( + responses.GET, + "https://remote.com/status/1", + json={ + "id": "https://remote.com/status/1", + "type": "Comment", + "published": "2021-04-05T18:04:59.735190+00:00", + "attributedTo": self.remote_user.remote_id, + "content": "

a comment

", + "to": ["https://www.w3.org/ns/activitystreams#Public"], + "cc": ["https://b875df3d118b.ngrok.io/user/mouse/followers"], + "inReplyTo": "", + "inReplyToBook": book.remote_id, + "summary": "", + "tag": [], + "sensitive": False, + "@context": "https://www.w3.org/ns/activitystreams" + } + ) + + with patch("bookwyrm.models.status.Status.ignore_activity") as discarder: + discarder.return_value = False + views.inbox.activity_task(activity) + self.assertTrue(redis_mock.called) + + boost = models.Boost.objects.get() + self.assertEqual(boost.boosted_status.remote_id, "https://remote.com/status/1") + self.assertEqual(boost.boosted_status.comment.status_type, "Comment") + self.assertEqual(boost.boosted_status.comment.book, book) + @responses.activate def test_handle_discarded_boost(self): """ test a boost of a mastodon status that will be discarded """ From a39cd670eff52d27a4145cb134a63d9726d7405d Mon Sep 17 00:00:00 2001 From: Mouse Reeve Date: Mon, 5 Apr 2021 18:05:06 -0700 Subject: [PATCH 12/14] Fixes boosted remote statuses coming in as Notes --- bookwyrm/activitypub/base_activity.py | 3 ++- bookwyrm/tests/views/test_inbox.py | 4 ++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/bookwyrm/activitypub/base_activity.py b/bookwyrm/activitypub/base_activity.py index 768eb2084..452f61e03 100644 --- a/bookwyrm/activitypub/base_activity.py +++ b/bookwyrm/activitypub/base_activity.py @@ -265,7 +265,8 @@ def resolve_remote_id( "Could not connect to host for remote_id in: %s" % (remote_id) ) # determine the model implicitly, if not provided - if not model: + # or if it's a model with subclasses like Status, check again + if not model or hasattr(model.objects, "select_subclasses"): model = get_model_from_type(data.get("type")) # check for existing items with shared unique identifiers diff --git a/bookwyrm/tests/views/test_inbox.py b/bookwyrm/tests/views/test_inbox.py index 6973f9814..f44a79c69 100644 --- a/bookwyrm/tests/views/test_inbox.py +++ b/bookwyrm/tests/views/test_inbox.py @@ -608,8 +608,8 @@ class Inbox(TestCase): "summary": "", "tag": [], "sensitive": False, - "@context": "https://www.w3.org/ns/activitystreams" - } + "@context": "https://www.w3.org/ns/activitystreams", + }, ) with patch("bookwyrm.models.status.Status.ignore_activity") as discarder: From fd66ff1861fbaaef572122c5d2636cb1e9ba067f Mon Sep 17 00:00:00 2001 From: Mouse Reeve Date: Tue, 6 Apr 2021 07:53:34 -0700 Subject: [PATCH 13/14] Small tweaks to commends and super() calls --- bookwyrm/activitystreams.py | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/bookwyrm/activitystreams.py b/bookwyrm/activitystreams.py index 2db6cc20a..949ae9dad 100644 --- a/bookwyrm/activitystreams.py +++ b/bookwyrm/activitystreams.py @@ -19,12 +19,12 @@ class ActivityStream(RedisStore): return "{}-unread".format(self.stream_id(user)) def get_rank(self, obj): # pylint: disable=no-self-use - """ the sort rank of a status, which is published date """ + """ statuses are sorted by date published """ return obj.published_date.timestamp() def add_status(self, status): """ add a status to users' feeds """ - # the pipeline contains all the addp-to-stream activities + # the pipeline contains all the add-to-stream activities pipeline = self.add_object_to_related_stores(status, execute=False) for user in self.get_audience(status): @@ -36,11 +36,13 @@ class ActivityStream(RedisStore): def add_user_statuses(self, viewer, user): """ add a user's statuses to another user's feed """ + # only add the statuses that the viewer should be able to see (ie, not dms) statuses = privacy_filter(viewer, user.status_set.all()) self.bulk_add_objects_to_store(statuses, self.stream_id(viewer)) def remove_user_statuses(self, viewer, user): """ remove a user's status from another user's feed """ + # remove all so that followers only statuses are removed statuses = user.status_set.all() self.bulk_remove_objects_from_store(statuses, self.stream_id(viewer)) @@ -49,7 +51,7 @@ class ActivityStream(RedisStore): # clear unreads for this feed r.set(self.unread_id(user), 0) - statuses = super().get_store(self.stream_id(user)) + statuses = self.get_store(self.stream_id(user)) return ( models.Status.objects.select_subclasses() .filter(id__in=statuses) @@ -62,7 +64,7 @@ class ActivityStream(RedisStore): def populate_streams(self, user): """ go from zero to a timeline """ - super().populate_store(self.stream_id(user)) + self.populate_store(self.stream_id(user)) def get_audience(self, status): # pylint: disable=no-self-use """ given a status, what users should see it """ From 1f99710dcdc17b5f7fb42ecfa8676da3d9e2c3e7 Mon Sep 17 00:00:00 2001 From: tofuwabohu Date: Tue, 6 Apr 2021 22:36:24 +0200 Subject: [PATCH 14/14] Links to own user in menu --- bookwyrm/templates/layout.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bookwyrm/templates/layout.html b/bookwyrm/templates/layout.html index 80eb386a5..0f100f2cd 100644 --- a/bookwyrm/templates/layout.html +++ b/bookwyrm/templates/layout.html @@ -70,7 +70,7 @@ {% if request.user.is_authenticated %}