diff --git a/AUTHORS.md b/AUTHORS.md index 6815fe6d28..25fd6160ef 100644 --- a/AUTHORS.md +++ b/AUTHORS.md @@ -2,46 +2,24 @@ Thank you all for contributing to the project, you are true heroes! 🫶 -*Generated on 2026-01-18* - ---- - ## Contributors - Github-actions - [https://github.com/invalid-email-address](https://github.com/invalid-email-address) - Roland Geider - [https://github.com/rolandgeider](https://github.com/rolandgeider) -- Justin - [https://github.com/justin-pinheiro](https://github.com/justin-pinheiro) -- Justin Pinheiro - [https://github.com/justin-pinheiro](https://github.com/justin-pinheiro) -- HairyCrabW - [https://github.com/HairyCrabW](https://github.com/HairyCrabW) -- Jeremy - [https://github.com/zru-1](https://github.com/zru-1) -- Yousef - [https://github.com/yza-cmu](https://github.com/yza-cmu) -- Chidinma Obiekwe - [https://github.com/DidiNDexter](https://github.com/DidiNDexter) -- Max Pylypenko - [https://github.com/3mpee3mpee](https://github.com/3mpee3mpee) -- AKSHIT - [https://github.com/Akshit-MMQH](https://github.com/Akshit-MMQH) -- Saad Muhammad - [https://github.com/saadpy0](https://github.com/saadpy0) -- JANVI - [https://github.com/JANVI-CHATURVEDI](https://github.com/JANVI-CHATURVEDI) -- Joseph - [https://github.com/seppzer0](https://github.com/seppzer0) -- Christián Feliks - [https://github.com/ChristianFeliks](https://github.com/ChristianFeliks) -- Shantanu - [https://github.com/shantanugupta2004](https://github.com/shantanugupta2004) -- Amandeep Mandal - [https://github.com/AmandeepMandal1077](https://github.com/AmandeepMandal1077) -- Ziqi Lin -- Dieter Plaetinck - [https://github.com/Dieterbe](https://github.com/Dieterbe) -- hangy - [https://github.com/hangy](https://github.com/hangy) -- Bonicki Wojciech -- Horacio Duran - [https://github.com/perrito666](https://github.com/perrito666) - Peter Dave Hello - [https://github.com/PeterDaveHello](https://github.com/PeterDaveHello) - Cam Cecil - [https://github.com/scrapcode](https://github.com/scrapcode) - Ali Rahbar - [https://github.com/crypto-a](https://github.com/crypto-a) - Joshua Shelley - [https://github.com/navyjosh](https://github.com/navyjosh) - Joshua - [https://github.com/navyjosh](https://github.com/navyjosh) - Ali Rahbar - [https://github.com/crypto-a](https://github.com/crypto-a) -- eyjhb - [https://github.com/eyJhb](https://github.com/eyJhb) - Matthew Harrison - [https://github.com/Maralai](https://github.com/Maralai) - Josh - [https://github.com/FlyingNimbusCloud](https://github.com/FlyingNimbusCloud) +- eyjhb - [https://github.com/eyJhb](https://github.com/eyJhb) - kmoy1 - [https://github.com/kmoy1](https://github.com/kmoy1) - Kevin Moy - [https://github.com/kmoy1](https://github.com/kmoy1) -- Victor B -- Taylor Fuller - [https://github.com/taylor-hileman](https://github.com/taylor-hileman) +- Victor B - [https://github.com/victorbmlabs](https://github.com/victorbmlabs) +- Dieter Plaetinck - [https://github.com/Dieterbe](https://github.com/Dieterbe) +- Taylor Fuller - [https://github.com/taylor-fuller](https://github.com/taylor-fuller) - JLaField - [https://github.com/JLaField](https://github.com/JLaField) - JLaField - [https://github.com/JLaField](https://github.com/JLaField) - bbk - [https://github.com/bbkz](https://github.com/bbkz) @@ -126,7 +104,7 @@ Thank you all for contributing to the project, you are true heroes! 🫶 - Withnale - Veltarn - [https://github.com/Veltarn](https://github.com/Veltarn) - Anjalie Kini -- gmmoraes - [https://github.com/Zutzsa](https://github.com/Zutzsa) +- gmmoraes - [https://github.com/gmmoraes](https://github.com/gmmoraes) - Peter van der Does - [https://github.com/petervanderdoes](https://github.com/petervanderdoes) - Daniel Topal - [https://github.com/dtopal](https://github.com/dtopal) - Mbarak Mbigo - [https://github.com/Mbarak-Mbigo](https://github.com/Mbarak-Mbigo) @@ -160,65 +138,78 @@ Thank you all for contributing to the project, you are true heroes! 🫶 ## Translators -### Arabic (Saudi Arabia) - -- E. Ta. -- MR - [https://github.com/MyaserAL-Healy](https://github.com/MyaserAL-Healy) -- SlyBat - [https://github.com/AW-G](https://github.com/AW-G) -- Hissabat Manager - [https://github.com/Chinguetti-Quizz](https://github.com/Chinguetti-Quizz) -- My Google - [https://github.com/ahmedtahraoui90](https://github.com/ahmedtahraoui90) - -### Bulgarian +### Portuguese +- Luiz Felipe Guidorizzi de Oliveira - [https://github.com/EvilMonark](https://github.com/EvilMonark) +- Wilton Rodrigues +- Guilherme Salomão - [https://github.com/salomaoparkour](https://github.com/salomaoparkour) +- Douglas Gabriel Ochner - [https://github.com/dochner](https://github.com/dochner) +- Guilherme G. Schardong - [https://github.com/schardong](https://github.com/schardong) +- Dalton Scavassa +- Felipe Nogueira - [https://github.com/fnogcps](https://github.com/fnogcps) +- Eduardo Menges Mattje - [https://github.com/EduMenges](https://github.com/EduMenges) +- João Goulart - [https://github.com/usehalter](https://github.com/usehalter) +- Roland Geider - [https://github.com/rolandgeider](https://github.com/rolandgeider) +- Ricardo Bezerra da Silva - [https://github.com/jeovajireh](https://github.com/jeovajireh) +- Raphael Marques de Barros +- ssantos - [https://github.com/SantosSi](https://github.com/SantosSi) - Allan Nordhøy - [https://github.com/comradekingu](https://github.com/comradekingu) -### Catalan +### Korean -- Zixu Sun - [https://github.com/ziixu](https://github.com/ziixu) -- guillem - [https://github.com/gbuendia](https://github.com/gbuendia) +- 고수처럼 - [https://github.com/rrrmaster](https://github.com/rrrmaster) +- Gyu-sun Youm - [https://github.com/Perlmint](https://github.com/Perlmint) +- 성동하 - [https://github.com/tomjovi](https://github.com/tomjovi) +- namong79 - [https://github.com/namong79](https://github.com/namong79) +- Allan Nordhøy - [https://github.com/comradekingu](https://github.com/comradekingu) -### Chinese (Simplified Han script) +### Finnish -- KW Lam - [https://github.com/loksonlkw](https://github.com/loksonlkw) -- Herb Huang +- Ricky Tigg - [https://github.com/Ricky-Tigg](https://github.com/Ricky-Tigg) +- Nikolay Korotkiy - [https://github.com/sikmir](https://github.com/sikmir) +- Juuso Haapanen - [https://github.com/juusohaapanen](https://github.com/juusohaapanen) +- J. Lavoie -### Chinese (Simplified) +### Polish -- Yi-Han Hsiung - [https://github.com/AaronHsiung](https://github.com/AaronHsiung) -- Tsz Hong CHAN - [https://github.com/tomyan112](https://github.com/tomyan112) -- Bingo_Yan - [https://github.com/ygbingo](https://github.com/ygbingo) -- Scotee007 - [https://github.com/Scotee007](https://github.com/Scotee007) -- cld4h - [https://github.com/cld4h](https://github.com/cld4h) -- yzqzss - [https://github.com/yzqzss](https://github.com/yzqzss) -- Jing - [https://github.com/jingcheng16](https://github.com/jingcheng16) -- sr-c - [https://github.com/sr-c](https://github.com/sr-c) -- tony - [https://github.com/tonyxxliu](https://github.com/tonyxxliu) -- freeair zhao -- 猫猫头 - [https://github.com/Lirosty](https://github.com/Lirosty) +- Karol Solecki - [https://github.com/karolsol](https://github.com/karolsol) +- Dawid Panyło +- Marcin Schoenknecht +- gnu-ewm +- A M - [https://github.com/AugiAugi44](https://github.com/AugiAugi44) +- Jacob - [https://github.com/devzom](https://github.com/devzom) +- Henio Szewczyk - [https://github.com/hszewczyk](https://github.com/hszewczyk) - Allan Nordhøy - [https://github.com/comradekingu](https://github.com/comradekingu) -### Croatian +### Romanian -- Milo Ivir - [https://github.com/milotype](https://github.com/milotype) +- B Sebastian +- Daniel Vigaru - [https://github.com/danielvigaru](https://github.com/danielvigaru) +- dimii27 - [https://github.com/dimii27](https://github.com/dimii27) +- Allan Nordhøy - [https://github.com/comradekingu](https://github.com/comradekingu) -### Czech +### Swedish -- Fjuro - [https://github.com/Fjuro](https://github.com/Fjuro) -- Fjuro -- CaptainDolphy - [https://github.com/CaptainDolphy](https://github.com/CaptainDolphy) -- Roman Kalivoda - [https://github.com/RKCZ](https://github.com/RKCZ) -- Zrnek - [https://github.com/TomasZmek](https://github.com/TomasZmek) +- PNS11 - [https://github.com/cess11](https://github.com/cess11) +- Kevin Gregard - [https://github.com/Kladdiskakan](https://github.com/Kladdiskakan) +- tygyh - [https://github.com/tygyh](https://github.com/tygyh) +- stefan - [https://github.com/clint1337](https://github.com/clint1337) - Allan Nordhøy - [https://github.com/comradekingu](https://github.com/comradekingu) -### Danish +### Russian -- Fedder Skovgaard - [https://github.com/fedders](https://github.com/fedders) -- Tomasz Cielecki - [https://github.com/Cheesebaron](https://github.com/Cheesebaron) +- Алексей Курышко - [https://github.com/alexkuryshko](https://github.com/alexkuryshko) +- Kira - [https://github.com/Balalaika87](https://github.com/Balalaika87) +- Nikolay Korotkiy - [https://github.com/sikmir](https://github.com/sikmir) +- Konstantin +- gudvinr +- Nikita Epifanov +- Ryeori - [https://github.com/daniqshu](https://github.com/daniqshu) - Roland Geider - [https://github.com/rolandgeider](https://github.com/rolandgeider) +- Allan Nordhøy - [https://github.com/comradekingu](https://github.com/comradekingu) ### Dutch -- Floris C - Christijan Mulder - Christijan - [https://github.com/ChrispyM](https://github.com/ChrispyM) - SilverServerT - [https://github.com/SilverServerT](https://github.com/SilverServerT) @@ -231,38 +222,51 @@ Thank you all for contributing to the project, you are true heroes! 🫶 - David - [https://github.com/DMachiels](https://github.com/DMachiels) - Allan Nordhøy - [https://github.com/comradekingu](https://github.com/comradekingu) -### Finnish +### Croatian -- Petri Hämäläinen - [https://github.com/pHamala](https://github.com/pHamala) -- Ricky Tigg - [https://github.com/Ricky-Tigg](https://github.com/Ricky-Tigg) -- Nikolay Korotkiy - [https://github.com/sikmir](https://github.com/sikmir) -- Juuso Haapanen - [https://github.com/juusohaapanen](https://github.com/juusohaapanen) -- J. Lavoie +- Milo Ivir - [https://github.com/milotype](https://github.com/milotype) -### French +### Hebrew -- Justin Pinheiro - [https://github.com/justin-pinheiro](https://github.com/justin-pinheiro) -- Paul Bonneau - [https://github.com/paulbonneau](https://github.com/paulbonneau) -- Kilian -- MrSniikyz - [https://github.com/BabyGeek](https://github.com/BabyGeek) -- florent4014 - [https://github.com/florent4014](https://github.com/florent4014) -- Lucas Batier - [https://github.com/lucas-batier](https://github.com/lucas-batier) -- yoyomax80400 - [https://github.com/yoyomax80400](https://github.com/yoyomax80400) -- Célian -- J. Lavoie +- shlomi assaf - [https://github.com/shlomiassaf](https://github.com/shlomiassaf) +- Tsz Hong CHAN - [https://github.com/tomyan112](https://github.com/tomyan112) +- n,rdo + +### Ukrainian + +- Максим Горпиніч +- kvinto - [https://github.com/badsystem](https://github.com/badsystem) +- Dan - [https://github.com/Kefir2105](https://github.com/Kefir2105) +- Dan +- Tymofii Lytvynenko +- Artem - [https://github.com/defaultpage](https://github.com/defaultpage) + +### Spanish + +- gallegonovato - [https://github.com/gallegonovato](https://github.com/gallegonovato) +- hhugoac - [https://github.com/hhugoac](https://github.com/hhugoac) +- Sergio Varela - [https://github.com/IngrownMink4](https://github.com/IngrownMink4) +- Javier - [https://github.com/dysk0zero](https://github.com/dysk0zero) +- Biologia Libros - [https://github.com/bio201922](https://github.com/bio201922) +- G. Buendía - [https://github.com/gbuendia](https://github.com/gbuendia) +- Sergio - [https://github.com/MrPointcut95](https://github.com/MrPointcut95) +- abraham +- Santiago Persico - [https://github.com/spersico](https://github.com/spersico) - Roland Geider - [https://github.com/rolandgeider](https://github.com/rolandgeider) -- Antoine Vibien - [https://github.com/r1llettes](https://github.com/r1llettes) +- Alex Hidalgo - [https://github.com/alexjhidalgo](https://github.com/alexjhidalgo) +- J. Lavoie - Allan Nordhøy - [https://github.com/comradekingu](https://github.com/comradekingu) +### Chinese (Simplified Han script) + +- Herb Huang + ### German -- Elias Lang - [https://github.com/elias170105](https://github.com/elias170105) -- Traladarer -- Richard Mrosk - [https://github.com/EtheriousNight](https://github.com/EtheriousNight) -- Roland Geider - [https://github.com/rolandgeider](https://github.com/rolandgeider) - kvnrmnn - [https://github.com/rmnn92](https://github.com/rmnn92) - Tobias Lechner - [https://github.com/Lxchnxr](https://github.com/Lxchnxr) - becothas - [https://github.com/becothas](https://github.com/becothas) +- Roland Geider - [https://github.com/rolandgeider](https://github.com/rolandgeider) - m4skedbyte - StefMe - [https://github.com/StefMe](https://github.com/StefMe) - Axel Steinbrecher @@ -272,175 +276,116 @@ Thank you all for contributing to the project, you are true heroes! 🫶 - mondstern - Allan Nordhøy - [https://github.com/comradekingu](https://github.com/comradekingu) -### Greek +### Hindi -- Antonis-geo -- George Koikas -- Eugenia Russell - [https://github.com/eugenia-russell](https://github.com/eugenia-russell) +- Ritik Sharma - [https://github.com/RitikSharma02](https://github.com/RitikSharma02) +- Ritish Bhardwaj - [https://github.com/levidroid](https://github.com/levidroid) + +### French + +- MrSniikyz - [https://github.com/BabyGeek](https://github.com/BabyGeek) +- florent4014 - [https://github.com/florent4014](https://github.com/florent4014) +- Lucas Batier - [https://github.com/lucas-batier](https://github.com/lucas-batier) +- yoyomax80400 - [https://github.com/yoyomax80400](https://github.com/yoyomax80400) +- Célian +- J. Lavoie +- Roland Geider - [https://github.com/rolandgeider](https://github.com/rolandgeider) +- Antoine Vibien - [https://github.com/r1llettes](https://github.com/r1llettes) - Allan Nordhøy - [https://github.com/comradekingu](https://github.com/comradekingu) -- Michalis -### Hebrew +### Persian -- Omer I.S - [https://github.com/omeritzics](https://github.com/omeritzics) -- DR -- shlomi assaf - [https://github.com/shlomiassaf](https://github.com/shlomiassaf) -- Tsz Hong CHAN - [https://github.com/tomyan112](https://github.com/tomyan112) -- n,rdo +- m93n pk - [https://github.com/m93n](https://github.com/m93n) +- anyBlackSoul +- keyvanmj - [https://github.com/keyvanmj](https://github.com/keyvanmj) +- Allan Nordhøy - [https://github.com/comradekingu](https://github.com/comradekingu) -### Hindi +### Norwegian Bokmål -- Madhav Agarwal - [https://github.com/gmrmad1](https://github.com/gmrmad1) -- Ritik Sharma - [https://github.com/RitikSharma02](https://github.com/RitikSharma02) -- Ritish Bhardwaj - [https://github.com/levidroid](https://github.com/levidroid) +- GS Bacon +- Allan Nordhøy - [https://github.com/comradekingu](https://github.com/comradekingu) +- Roland Geider - [https://github.com/rolandgeider](https://github.com/rolandgeider) -### Hungarian +### Catalan -- Adam Cool -- László Mócsy - [https://github.com/LamaxStudio](https://github.com/LamaxStudio) +- Zixu Sun - [https://github.com/ziixu](https://github.com/ziixu) +- guillem - [https://github.com/gbuendia](https://github.com/gbuendia) + +### Turkish + +- Oğuz Ersen - [https://github.com/oersen](https://github.com/oersen) +- Cem Avcı - [https://github.com/cem256](https://github.com/cem256) +- Oğuz Ersen +- Allan Nordhøy - [https://github.com/comradekingu](https://github.com/comradekingu) + +### Chinese (Simplified) + +- Yi-Han Hsiung - [https://github.com/AaronHsiung](https://github.com/AaronHsiung) +- Tsz Hong CHAN - [https://github.com/tomyan112](https://github.com/tomyan112) +- Bingo_Yan - [https://github.com/ygbingo](https://github.com/ygbingo) +- Scotee007 - [https://github.com/Scotee007](https://github.com/Scotee007) +- cld4h - [https://github.com/cld4h](https://github.com/cld4h) +- yzqzss - [https://github.com/yzqzss](https://github.com/yzqzss) +- Jing - [https://github.com/jingcheng16](https://github.com/jingcheng16) +- sr-c - [https://github.com/sr-c](https://github.com/sr-c) +- tony - [https://github.com/tonyxxliu](https://github.com/tonyxxliu) +- freeair zhao +- 猫猫头 - [https://github.com/Lirosty](https://github.com/Lirosty) - Allan Nordhøy - [https://github.com/comradekingu](https://github.com/comradekingu) ### Italian -- Luca Galli - [https://github.com/Lvcaa](https://github.com/Lvcaa) -- clafalco - [https://github.com/clafalco](https://github.com/clafalco) - Federico Pierantoni - [https://github.com/F3FFO](https://github.com/F3FFO) - oarion - [https://github.com/oarion](https://github.com/oarion) - Alessandro Faucci - [https://github.com/Dhy19971](https://github.com/Dhy19971) - Marco Accorinti - [https://github.com/accodev](https://github.com/accodev) - Armando La Placa - [https://github.com/a-lp](https://github.com/a-lp) +- clafalco - [https://github.com/clafalco](https://github.com/clafalco) - DT - MARCO ACORTE - [https://github.com/marco-acorte](https://github.com/marco-acorte) - Roland Geider - [https://github.com/rolandgeider](https://github.com/rolandgeider) - J. Lavoie - Stefano Rossi - [https://github.com/stefanorossiti](https://github.com/stefanorossiti) -### Japanese - -- Ryohei Morimoto - [https://github.com/Ryohei-Caulked](https://github.com/Ryohei-Caulked) -- sasukeiscool - [https://github.com/sasukeiscool](https://github.com/sasukeiscool) -- yuki chi - [https://github.com/kumo2kumo](https://github.com/kumo2kumo) - -### Korean - -- kobo -- 고수처럼 - [https://github.com/rrrmaster](https://github.com/rrrmaster) -- Gyu-sun Youm - [https://github.com/Perlmint](https://github.com/Perlmint) -- 성동하 - [https://github.com/tomjovi](https://github.com/tomjovi) -- namong79 - [https://github.com/namong79](https://github.com/namong79) -- Allan Nordhøy - [https://github.com/comradekingu](https://github.com/comradekingu) - -### Norwegian Bokmål - -- GS Bacon - [https://github.com/Z0ink5](https://github.com/Z0ink5) -- Allan Nordhøy - [https://github.com/comradekingu](https://github.com/comradekingu) -- Roland Geider - [https://github.com/rolandgeider](https://github.com/rolandgeider) - -### Persian - -- Mahmuoud Salehi -- William Sky - [https://github.com/William-cloud-tech](https://github.com/William-cloud-tech) -- m93n pk - [https://github.com/m93n](https://github.com/m93n) -- anyBlackSoul -- keyvanmj - [https://github.com/keyvanmj](https://github.com/keyvanmj) -- Allan Nordhøy - [https://github.com/comradekingu](https://github.com/comradekingu) - -### Polish +### Czech -- Karol Solecki - [https://github.com/karolsol](https://github.com/karolsol) -- Dawid Panyło -- Marcin Schoenknecht -- gnu-ewm - [https://github.com/e-michalak](https://github.com/e-michalak) -- A M - [https://github.com/AugiAugi44](https://github.com/AugiAugi44) -- Jacob - [https://github.com/devzom](https://github.com/devzom) -- Henio Szewczyk - [https://github.com/hszewczyk](https://github.com/hszewczyk) +- Fjuro - [https://github.com/Fjuro](https://github.com/Fjuro) +- Fjuro +- CaptainDolphy - [https://github.com/CaptainDolphy](https://github.com/CaptainDolphy) +- Roman Kalivoda - [https://github.com/RKCZ](https://github.com/RKCZ) +- Zrnek - [https://github.com/TomasZmek](https://github.com/TomasZmek) - Allan Nordhøy - [https://github.com/comradekingu](https://github.com/comradekingu) -### Portuguese +### Hungarian -- Ninguém Mesmo -- Luiz Felipe Guidorizzi de Oliveira - [https://github.com/EvilMonark](https://github.com/EvilMonark) -- Wilton Rodrigues -- Guilherme Salomão - [https://github.com/salomaoparkour](https://github.com/salomaoparkour) -- Douglas Gabriel Ochner - [https://github.com/dochner](https://github.com/dochner) -- Guilherme G. Schardong - [https://github.com/schardong](https://github.com/schardong) -- Dalton Scavassa -- Felipe Nogueira - [https://github.com/fnogcps](https://github.com/fnogcps) -- Eduardo Menges Mattje - [https://github.com/EduMenges](https://github.com/EduMenges) -- João Goulart - [https://github.com/usehalter](https://github.com/usehalter) -- Roland Geider - [https://github.com/rolandgeider](https://github.com/rolandgeider) -- Ricardo Bezerra da Silva - [https://github.com/jeovajireh](https://github.com/jeovajireh) -- Raphael Marques de Barros -- ssantos - [https://github.com/SantosSi](https://github.com/SantosSi) +- Adam Cool +- László Mócsy - [https://github.com/LamaxStudio](https://github.com/LamaxStudio) - Allan Nordhøy - [https://github.com/comradekingu](https://github.com/comradekingu) -### Romanian +### Japanese -- Vlad Bejenaru - [https://github.com/vladbejenaru](https://github.com/vladbejenaru) -- B Sebastian -- Daniel Vigaru - [https://github.com/danielvigaru](https://github.com/danielvigaru) -- dimii27 - [https://github.com/dimii27](https://github.com/dimii27) -- Allan Nordhøy - [https://github.com/comradekingu](https://github.com/comradekingu) +- sasukeiscool - [https://github.com/sasukeiscool](https://github.com/sasukeiscool) +- yuki chi - [https://github.com/kumo2kumo](https://github.com/kumo2kumo) -### Russian +### Greek -- Gevorg Danielyan - [https://github.com/Cyworc](https://github.com/Cyworc) -- Иван Редун - [https://github.com/ireduntr](https://github.com/ireduntr) -- Iskander - [https://github.com/iskanderCabbie](https://github.com/iskanderCabbie) -- Maksim_220 Кабанов -- Алексей Курышко - [https://github.com/alexkuryshko](https://github.com/alexkuryshko) -- Kira - [https://github.com/Balalaika87](https://github.com/Balalaika87) -- Nikolay Korotkiy - [https://github.com/sikmir](https://github.com/sikmir) -- Konstantin -- gudvinr -- Nikita Epifanov -- Ryeori - [https://github.com/daniqshu](https://github.com/daniqshu) -- Roland Geider - [https://github.com/rolandgeider](https://github.com/rolandgeider) +- Antonis-geo - [https://github.com/Antonis-geo](https://github.com/Antonis-geo) +- George Koikas +- Eugenia Russell - [https://github.com/eugenia-russell](https://github.com/eugenia-russell) - Allan Nordhøy - [https://github.com/comradekingu](https://github.com/comradekingu) +- Michalis -### Spanish +### Danish -- v7mbz -- Jose David Villegas (JoseDv1) - [https://github.com/JoseDv1](https://github.com/JoseDv1) -- Oscar González - [https://github.com/ogrydc](https://github.com/ogrydc) +- Tomasz Cielecki - [https://github.com/Cheesebaron](https://github.com/Cheesebaron) - Roland Geider - [https://github.com/rolandgeider](https://github.com/rolandgeider) -- gallegonovato - [https://github.com/gallegonovato](https://github.com/gallegonovato) -- hhugoac - [https://github.com/hhugoac](https://github.com/hhugoac) -- Sergio Varela - [https://github.com/IngrownMink4](https://github.com/IngrownMink4) -- Javier - [https://github.com/dysk0zero](https://github.com/dysk0zero) -- Biologia Libros - [https://github.com/bio201922](https://github.com/bio201922) -- G. Buendía - [https://github.com/gbuendia](https://github.com/gbuendia) -- Sergio - [https://github.com/MrPointcut95](https://github.com/MrPointcut95) -- abraham -- Santiago Persico - [https://github.com/spersico](https://github.com/spersico) -- Alex Hidalgo - [https://github.com/alexjhidalgo](https://github.com/alexjhidalgo) -- J. Lavoie -- Allan Nordhøy - [https://github.com/comradekingu](https://github.com/comradekingu) -### Swedish +### Arabic (Saudi Arabia) -- wonnock - [https://github.com/emgust](https://github.com/emgust) -- PNS11 - [https://github.com/cess11](https://github.com/cess11) -- Kevin Gregard - [https://github.com/Kladdiskakan](https://github.com/Kladdiskakan) -- tygyh - [https://github.com/tygyh](https://github.com/tygyh) -- stefan - [https://github.com/clint1337](https://github.com/clint1337) -- Allan Nordhøy - [https://github.com/comradekingu](https://github.com/comradekingu) +- SlyBat - [https://github.com/AW-G](https://github.com/AW-G) +- Hissabat Manager - [https://github.com/Chinguetti-Quizz](https://github.com/Chinguetti-Quizz) +- My Google - [https://github.com/ahmedtahraoui90](https://github.com/ahmedtahraoui90) -### Turkish +### Bulgarian -- Oğuz Ersen - [https://github.com/oersen](https://github.com/oersen) -- Cem Avcı - [https://github.com/cem256](https://github.com/cem256) -- Oğuz Ersen - Allan Nordhøy - [https://github.com/comradekingu](https://github.com/comradekingu) - -### Ukrainian - -- Максим Горпиніч - [https://github.com/maksim2005UKR](https://github.com/maksim2005UKR) -- Максим Горпиніч - [https://github.com/Maksim2005UA](https://github.com/Maksim2005UA) -- Максим Горпиніч -- Максим Горпиніч -- kvinto - [https://github.com/badsystem](https://github.com/badsystem) -- Dan - [https://github.com/Kefir2105](https://github.com/Kefir2105) -- Dan -- Tymofii Lytvynenko -- Artem - [https://github.com/defaultpage](https://github.com/defaultpage) diff --git a/wger/core/static/css/react-simple-wysiwyg.css b/wger/core/static/css/react-simple-wysiwyg.css new file mode 100644 index 0000000000..5579898ed4 --- /dev/null +++ b/wger/core/static/css/react-simple-wysiwyg.css @@ -0,0 +1,89 @@ +/* + This file is part of Workout Manager + + Workout Manager is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Workout Manager is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with Workout Manager. If not, see . +*/ + +/* + * Hack: this is the copied CSS from https://github.com/megahertz/react-simple-wysiwyg + * so that the styles are loaded when loading this in a shadow dom in the django + * application. + */ +.rsw-editor { + border: 1px solid #ddd; + border-radius: 0.375rem; + display: flex; + flex-direction: column; + min-height: 100px; + overflow: hidden; +} + +.rsw-ce { + flex: 1 0 auto; + padding: 0.5rem; +} + +.rsw-ce:focus { + outline: 1px solid #668; +} + +.rsw-ce[contentEditable='true']:empty:not(:focus):before { + content: attr(placeholder); + color: grey; +} + +.rsw-html { + background: transparent; + border: none; + font-family: monospace, 'Courier New'; +} + +.rsw-separator { + align-self: stretch; + border-right: 1px solid #ddd; + display: flex; + margin: 0 3px; +} + +.rsw-dd { + box-sizing: border-box; + outline: none; +} + +.rsw-btn { + background: transparent; + border: 0; + cursor: pointer; + color: #222; + font-size: 1em; + height: 2em; + outline: none; + padding: 0; + width: 2em; +} + +.rsw-btn:hover { + background: #eaeaea; +} + +.rsw-btn[data-active='true'] { + background: #e0e0e0; +} + +.rsw-toolbar { + align-items: center; + background-color: #f5f5f5; + border-bottom: 1px solid #ddd; + display: flex; +} diff --git a/wger/core/templates/index.html b/wger/core/templates/index.html new file mode 100644 index 0000000000..b1e5641c4e --- /dev/null +++ b/wger/core/templates/index.html @@ -0,0 +1,31 @@ +{% extends "base_wide.html" %} +{% load i18n static wger_extras %} + +{% block title %}{% translate "Dashboard" %}{% endblock %} + +{# #} +{# Header #} +{# #} +{% block header %} +{% endblock %} + + + +{% block content %} +
+
+
+
+
+ +
+
+
+ +
+
+
+ +
+
+{% endblock %} diff --git a/wger/manager/models/log.py.save b/wger/manager/models/log.py.save new file mode 100644 index 0000000000..476b9320b6 --- /dev/null +++ b/wger/manager/models/log.py.save @@ -0,0 +1,302 @@ +# This file is part of wger Workout Manager . +# Copyright (C) 2013 - 2021 wger Team +# +# wger Workout Manager is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# wger Workout Manager is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . + +# Standard Library +import datetime + +# Django +from django.contrib.auth.models import User +^X + +from django.core.exceptions import ValidationError +from django.db import models +wp +from django.utils.translation import gettext_lazy as _ + +# wger +from wger.core.models import ( + RepetitionUnit, + WeightUnit, +) +from wger.exercises.models import Exercise +from wger.manager.consts import ( + REP_UNIT_REPETITIONS, + WEIGHT_UNIT_KG, +) +from wger.manager.managers import WorkoutLogManager +from wger.manager.models.session import WorkoutSession +from wger.manager.validators import ( + NullMinValueValidator, + validate_rir, +) +from wger.utils.cache import reset_workout_log + + +class WorkoutLog(models.Model): + """ + A log entry for an exercise + """ + + objects = WorkoutLogManager() + + date = models.DateTimeField( + verbose_name=_('Date'), + default=datetime.datetime.now, + ) + + user = models.ForeignKey( + User, + verbose_name=_('User'), + editable=False, + on_delete=models.CASCADE, + ) + + next_log = models.ForeignKey( + 'self', + editable=True, + on_delete=models.CASCADE, + null=True, + default=None, + ) + """ + If this is a log entry for a dropset or similar, this field will contain the + next log entry in the series + """ + + session = models.ForeignKey( + 'WorkoutSession', + verbose_name=_('Session'), + on_delete=models.CASCADE, + null=True, + related_name='logs', + ) + """ + The session this log belongs to. + + If none is given, one will be automatically created on save. + """ + + exercise = models.ForeignKey( + Exercise, + verbose_name=_('Exercise'), + on_delete=models.CASCADE, + ) + + routine = models.ForeignKey( + 'Routine', + verbose_name=_('Workout'), + on_delete=models.CASCADE, + null=True, + ) + + slot_entry = models.ForeignKey( + 'SlotEntry', + on_delete=models.CASCADE, + null=True, + ) + + iteration = models.PositiveIntegerField( + null=True, + ) + + repetitions_unit = models.ForeignKey( + RepetitionUnit, + verbose_name=_('Unit'), + default=REP_UNIT_REPETITIONS, + on_delete=models.CASCADE, + null=True, + blank=True, + ) + """ + The repetition unit of the log. This can be e.g. a repetition, a minute, etc. + """ + + repetitions = models.DecimalField( + max_digits=6, + decimal_places=2, + validators=[NullMinValueValidator(0)], + blank=True, + null=True, + ) + """ + Logged amount of repetitions + """ + + repetitions_target = models.DecimalField( + max_digits=6, + decimal_places=2, + verbose_name=_('Repetitions'), + validators=[NullMinValueValidator(0)], + null=True, + blank=True, + ) + """ + Target amount of repetitions + """ + + weight_unit = models.ForeignKey( + WeightUnit, + verbose_name=_('Unit'), + default=WEIGHT_UNIT_KG, + on_delete=models.CASCADE, + null=True, + blank=True, + ) + """ + The weight unit of the log. This can be e.g. kg, lb, km/h, etc. + """ + + weight = models.DecimalField( + max_digits=6, + decimal_places=2, + validators=[NullMinValueValidator(0)], + blank=True, + null=True, + ) + """ + Logged amount of weight + """ + + weight_target = models.DecimalField( + max_digits=6, + decimal_places=2, + verbose_name=_('Weight'), + validators=[NullMinValueValidator(0)], + null=True, + blank=True, + ) + """ + Target amount of weight + """ + + rir = models.DecimalField( + max_digits=2, + decimal_places=1, + validators=[NullMinValueValidator(0), validate_rir], + null=True, + blank=True, + ) + """ + Reps in Reserve, RiR. The amount of reps that could realistically still be + done in the set. + """ + + rir_target = models.DecimalField( + max_digits=2, + decimal_places=1, + validators=[NullMinValueValidator(0), validate_rir], + null=True, + blank=True, + ) + """ + Target Reps in Reserve + """ + + rest = models.PositiveIntegerField( + blank=True, + null=True, + ) + """ + Logged rest time + """ + + rest_target = models.PositiveIntegerField( + blank=True, + null=True, + ) + """ + Target rest time + """ + + # Metaclass to set some other properties + class Meta: + ordering = ['date', 'repetitions', 'weight'] + + def __str__(self): + """ + Return a more human-readable representation + """ + return f'Log entry: {self.repetitions} - {self.weight} kg on {self.date}' + + def get_owner_object(self): + """ + Returns the object that has owner information + """ + return self + + def clean(self): + super().clean() + if self.repetitions is None and self.weight is None: + raise ValidationError('Both repetitions and weight cannot be null at the same time.') + + if self.repetitions is not None and self.repetitions_unit is None: + raise ValidationError('Repetitions unit must be present if repetitions have a value.') + + if self.weight is not None and self.weight_unit is None: + raise ValidationError('Weight unit must be present if weight has a value.') + + def save(self, *args, **kwargs): + """ + Plumbing + """ + self.clean() + + # If the routine does not belong to this user, do not save + if self.routine and self.routine.user != self.user: + return + + # If there is no session for this date and routine, create one + self.session = WorkoutSession.objects.get_or_create( + user=self.user, + date=self.date, + routine=self.routine, + )[0] + + # Reset cache + reset_workout_log( + self.user_id, + self.session.date.year, + self.session.date.month, + self.session.date.day, + ) + + # If the user of next_log is not this user, remove foreign key + if self.next_log and self.next_log.user != self.user: + self.next_log = None + + # If the user selected "Until Failure", do only 1 "repetition", + # anything else doesn't make sense. + if self.repetitions_unit == 2: + self.reps = 1 + + # Save to db + super().save(*args, **kwargs) + + def delete(self, *args, **kwargs): + """ + Reset cache + """ + try: + reset_workout_log( + self.user_id, + self.session.date.year, + self.session.date.month, + self.session.date.day, + ) + # Catch case when there is no session -> RelatedObjectDoesNotExist + except WorkoutSession.DoesNotExist: + pass + super(WorkoutLog, self).delete(*args, **kwargs) diff --git a/wger/nutrition/api/serializers.py b/wger/nutrition/api/serializers.py index e5b616c28d..dae96a3973 100644 --- a/wger/nutrition/api/serializers.py +++ b/wger/nutrition/api/serializers.py @@ -30,6 +30,7 @@ MealItem, NutritionPlan, WeightUnit, + FastingWindow, ) @@ -380,3 +381,23 @@ class Meta: 'goal_fiber', 'meals', ) +class FastingWindowSerializer(serializers.ModelSerializer): + """ + Fasting window serializer + """ + + duration_seconds = serializers.IntegerField(read_only=True) + + class Meta: + model = FastingWindow + fields = ( + 'id', + 'start', + 'end', + 'goal_duration_minutes', + 'note', + 'created', + 'updated', + 'duration_seconds', + ) + read_only_fields = ('id', 'created', 'updated', 'duration_seconds') diff --git a/wger/nutrition/api/views.py b/wger/nutrition/api/views.py index 674c1336ee..438fb159ed 100644 --- a/wger/nutrition/api/views.py +++ b/wger/nutrition/api/views.py @@ -27,7 +27,7 @@ # Third Party from easy_thumbnails.alias import aliases from easy_thumbnails.files import get_thumbnailer -from rest_framework import viewsets +from rest_framework import viewsets, permissions from rest_framework.decorators import ( action, api_view, @@ -51,6 +51,7 @@ NutritionPlanInfoSerializer, NutritionPlanSerializer, WeightUnitSerializer, + FastingWindowSerializer, ) from wger.nutrition.forms import UnitChooserForm from wger.nutrition.models import ( @@ -62,6 +63,7 @@ MealItem, NutritionPlan, WeightUnit, + FastingWindow, ) from wger.utils.constants import ( ENGLISH_SHORT_NAME, @@ -143,21 +145,13 @@ class IngredientInfoViewSet(IngredientViewSet): serializer_class = IngredientInfoSerializer - def get_queryset(self): - """Optimize the queryset with select_related to avoid n+1 queries""" - - return Ingredient.objects.select_related( - 'language', - 'license', - 'image', - ) - class ImageViewSet(viewsets.ReadOnlyModelViewSet): """ API endpoint for ingredient images """ + queryset = Image.objects.all() serializer_class = IngredientImageSerializer ordering_fields = '__all__' filterset_fields = ('uuid', 'ingredient_id', 'ingredient__uuid') @@ -166,11 +160,6 @@ class ImageViewSet(viewsets.ReadOnlyModelViewSet): def dispatch(self, request, *args, **kwargs): return super().dispatch(request, *args, **kwargs) - def get_queryset(self): - """Optimize the queryset""" - - return Image.objects.select_related('ingredient') - class WeightUnitViewSet(viewsets.ReadOnlyModelViewSet): """ @@ -375,3 +364,26 @@ def nutritional_values(self, request, pk): return Response( LogItem.objects.get(pk=pk, plan__user=self.request.user).get_nutritional_values() ) + + + + +class FastingWindowViewSet(viewsets.ModelViewSet): + """ + API endpoint for fasting windows. + + - Only returns the current user's windows + - Requires authentication + """ + + serializer_class = FastingWindowSerializer + permission_classes = (permissions.IsAuthenticated,) + + def get_queryset(self): + user = self.request.user + if not user.is_authenticated: + return FastingWindow.objects.none() + return FastingWindow.objects.filter(user=user).order_by("-start") + + def perform_create(self, serializer): + serializer.save(user=self.request.user) diff --git a/wger/nutrition/migrations/0029_add_fasting_window.py b/wger/nutrition/migrations/0029_add_fasting_window.py new file mode 100644 index 0000000000..af758e287d --- /dev/null +++ b/wger/nutrition/migrations/0029_add_fasting_window.py @@ -0,0 +1,73 @@ +# Generated by Django 5.2.7 on 2025-12-05 17:50 + +import datetime +import django.db.models.deletion +import django.utils.timezone +from django.conf import settings +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ("nutrition", "0028_ingredient_dietary_properties"), + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ] + + operations = [ + migrations.CreateModel( + name="FastingWindow", + fields=[ + ( + "id", + models.AutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ( + "start", + models.DateTimeField( + default=django.utils.timezone.now, verbose_name="Start of fast" + ), + ), + ( + "end", + models.DateTimeField( + blank=True, null=True, verbose_name="End of fast" + ), + ), + ( + "goal_duration_minutes", + models.PositiveIntegerField( + blank=True, + help_text="Optional target length for this fast.", + null=True, + verbose_name="Goal duration (minutes)", + ), + ), + ( + "note", + models.CharField(blank=True, max_length=255, verbose_name="Notes"), + ), + ("created", models.DateTimeField(auto_now_add=True)), + ("updated", models.DateTimeField(auto_now=True)), + ], + options={ + "verbose_name": "Fasting window", + "verbose_name_plural": "Fasting windows", + "ordering": ["-start"], + }, + ), + migrations.AddField( + model_name="fastingwindow", + name="user", + field=models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, + to=settings.AUTH_USER_MODEL, + verbose_name="User", + ), + ), + ] diff --git a/wger/nutrition/models/__init__.py b/wger/nutrition/models/__init__.py index 00e2a1f319..1a16eab151 100644 --- a/wger/nutrition/models/__init__.py +++ b/wger/nutrition/models/__init__.py @@ -25,3 +25,4 @@ from .plan import NutritionPlan from .sources import Source from .weight_unit import WeightUnit +from .fasting import FastingWindow diff --git a/wger/nutrition/models/fasting.py b/wger/nutrition/models/fasting.py new file mode 100644 index 0000000000..ae6ff83a63 --- /dev/null +++ b/wger/nutrition/models/fasting.py @@ -0,0 +1,76 @@ +# wger/nutrition/models/fasting.py + +import datetime + +from django.contrib.auth.models import User +from django.db import models +from django.utils import timezone +from django.utils.translation import gettext_lazy as _ + + +class FastingWindow(models.Model): + """ + A time window where the user is fasting. + + If `end` is null, the fast is currently in progress. + """ + + user = models.ForeignKey( + User, + verbose_name=_('User'), + on_delete=models.CASCADE, + ) + + start = models.DateTimeField( + verbose_name=_('Start of fast'), + default=timezone.now, + ) + + end = models.DateTimeField( + verbose_name=_('End of fast'), + null=True, + blank=True, + ) + + goal_duration_minutes = models.PositiveIntegerField( + verbose_name=_('Goal duration (minutes)'), + null=True, + blank=True, + help_text=_('Optional target length for this fast.'), + ) + + note = models.CharField( + verbose_name=_('Notes'), + max_length=255, + blank=True, + ) + + created = models.DateTimeField(auto_now_add=True) + updated = models.DateTimeField(auto_now=True) + + class Meta: + ordering = ['-start'] + verbose_name = _('Fasting window') + verbose_name_plural = _('Fasting windows') + + def __str__(self): + return f'{self.user} fast starting {self.start}' + + def get_owner_object(self): + """ + Used by wger's permission helpers. + """ + return self + + @property + def is_active(self) -> bool: + return self.end is None + + @property + def duration(self) -> datetime.timedelta: + end = self.end or timezone.now() + return end - self.start + + @property + def duration_seconds(self) -> int: + return int(self.duration.total_seconds()) diff --git a/wger/nutrition/tests/test_fasting.py b/wger/nutrition/tests/test_fasting.py new file mode 100644 index 0000000000..87c0b7cce5 --- /dev/null +++ b/wger/nutrition/tests/test_fasting.py @@ -0,0 +1,121 @@ +import datetime + +from django.test import TestCase +from django.utils import timezone +from django.contrib.auth import get_user_model + +from wger.nutrition.models.fasting import FastingWindow +from wger.core.models.language import Language + + +User = get_user_model() + + +class FastingWindowTestCase(TestCase): + def setUp(self) -> None: + Language.objects.get_or_create( + pk=2, + defaults={ + "short_name": "en", + "full_name": "English", + "full_name_en": "English", + }, + ) + + self.user = User.objects.create_user( + username="fasting_tester", + email="tester@example.com", + password="secure-password", + ) + + def test_create_minimal_fasting_window(self): + window = FastingWindow.objects.create(user=self.user) + + self.assertEqual(window.user, self.user) + self.assertIsNotNone(window.start) + self.assertIsNone(window.end) + self.assertIsNone(window.goal_duration_minutes) + self.assertEqual(window.note, "") + + def test_is_active_true_when_end_is_none(self): + window = FastingWindow.objects.create( + user=self.user, + end=None, + ) + self.assertTrue(window.is_active) + + def test_is_active_false_when_end_is_set(self): + now = timezone.now() + later = now + datetime.timedelta(hours=1) + + window = FastingWindow.objects.create( + user=self.user, + start=now, + end=later, + ) + self.assertFalse(window.is_active) + + def test_duration_and_duration_seconds_for_finished_fast(self): + start = timezone.now() + end = start + datetime.timedelta(hours=1, minutes=30) # 1.5 hours + + window = FastingWindow.objects.create( + user=self.user, + start=start, + end=end, + ) + + expected_delta = end - start + self.assertEqual(window.duration, expected_delta) + self.assertEqual(window.duration_seconds, int(expected_delta.total_seconds())) + self.assertEqual(window.duration_seconds, 90 * 60) + + def test_duration_for_active_fast_is_positive(self): + window = FastingWindow.objects.create(user=self.user) + self.assertTrue(window.is_active) + + duration = window.duration + self.assertGreater(duration.total_seconds(), 0) + + def test_goal_duration_minutes_optional_and_stored(self): + window_without_goal = FastingWindow.objects.create(user=self.user) + self.assertIsNone(window_without_goal.goal_duration_minutes) + + goal_minutes = 16 * 60 + window_with_goal = FastingWindow.objects.create( + user=self.user, + goal_duration_minutes=goal_minutes, + ) + self.assertEqual(window_with_goal.goal_duration_minutes, goal_minutes) + + def test_str_representation_contains_user_and_start(self): + start = timezone.now() + window = FastingWindow.objects.create( + user=self.user, + start=start, + ) + + text = str(window) + self.assertIn(self.user.username, text) + self.assertIn(str(start.date()), text) + + def test_get_owner_object_returns_self(self): + window = FastingWindow.objects.create(user=self.user) + self.assertIs(window.get_owner_object(), window) + + def test_ordering_by_start_descending(self): + older_start = timezone.now() - datetime.timedelta(hours=5) + newer_start = timezone.now() + + older = FastingWindow.objects.create( + user=self.user, + start=older_start, + ) + newer = FastingWindow.objects.create( + user=self.user, + start=newer_start, + ) + + windows = list(FastingWindow.objects.all()) + self.assertEqual(windows[0], newer) + self.assertEqual(windows[1], older) diff --git a/wger/urls.py b/wger/urls.py index fbb9138c34..aff386b336 100644 --- a/wger/urls.py +++ b/wger/urls.py @@ -249,6 +249,11 @@ measurements_api_views.CategoryViewSet, basename='measurement-category', ) +router.register( + r'fastingwindow', + nutrition_api_views.FastingWindowViewSet, + basename='api-fastingwindow', +) # Trophies app router.register(r'trophy', trophies_api_views.TrophyViewSet, basename='trophy')