fix: show "knock accepted" push notification body instead of "You have been invited" (#5823) (#5835)
* fix: show knock-accepted notification body when invite follows a knock (#5823) When Synapse accepts a knock it sends an m.room.member invite event. The client was displaying the generic 'You have been invited by X' push notification body because the invite event alone doesn't carry prev_content on the push path. Use KnockTracker (already used for auto-join) to detect knock-accepted invites and show a dedicated 'Your join request was accepted!' notification body instead. - Add knockAccepted string to intl_en.arb - Extract condition into isKnockAcceptedInvite() pure util for testability - Expose KnockTracker.getKnockedRoomIds() publicly (was _getKnockedRoomIds) - Override notification body in push_helper.dart (background) and local_notifications_extension.dart (foreground/web) - Unit tests for all isKnockAcceptedInvite() branches (9 tests) * formatting * fix up pangea comments * fix: avoid race condition with knocked room account data updates and local push notification content * translations --------- Co-authored-by: ggurdin <ggurdin@gmail.com>
This commit is contained in:
parent
f0da1663ce
commit
6f32aab48b
65 changed files with 1580 additions and 182 deletions
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"@@locale": "ar",
|
||||
"@@last_modified": "2026-02-24 14:59:40.158437",
|
||||
"@@last_modified": "2026-02-27 12:23:01.172808",
|
||||
"about": "حول",
|
||||
"@about": {
|
||||
"type": "String",
|
||||
|
|
@ -11650,5 +11650,25 @@
|
|||
"@cannotJoinBannedRoom": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"knockAccepted": "تم قبول طلب انضمامك! يمكنك الآن دخول الدورة.",
|
||||
"sessionFull": "لقد فات الأوان! هذه النشاط ممتلئ.",
|
||||
"returnToCourse": "العودة إلى الدورة",
|
||||
"returnHome": "العودة إلى الصفحة الرئيسية",
|
||||
"@knockAccepted": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"@sessionFull": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"@returnToCourse": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"@returnHome": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
}
|
||||
}
|
||||
|
|
@ -4619,7 +4619,7 @@
|
|||
"playWithAI": "Пакуль гуляйце з ШІ",
|
||||
"courseStartDesc": "Pangea Bot гатовы да працы ў любы час!\n\n...але навучанне лепш з сябрамі!",
|
||||
"@@locale": "be",
|
||||
"@@last_modified": "2026-02-24 14:59:33.504013",
|
||||
"@@last_modified": "2026-02-27 12:22:44.541648",
|
||||
"@ignore": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
|
|
@ -11319,5 +11319,25 @@
|
|||
"@cannotJoinBannedRoom": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"knockAccepted": "Ваш запыт на ўдзел быў прыняты! Цяпер вы можаце ўвайсці ў курс.",
|
||||
"sessionFull": "Занадта позна! Гэтая актыўнасць запоўнена.",
|
||||
"returnToCourse": "Вярнуцца да курса",
|
||||
"returnHome": "Вярнуцца дадому",
|
||||
"@knockAccepted": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"@sessionFull": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"@returnToCourse": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"@returnHome": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
{
|
||||
"@@last_modified": "2026-02-24 14:59:47.679831",
|
||||
"@@last_modified": "2026-02-27 12:23:20.380952",
|
||||
"about": "সম্পর্কে",
|
||||
"@about": {
|
||||
"type": "String",
|
||||
|
|
@ -12044,5 +12044,25 @@
|
|||
"@cannotJoinBannedRoom": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"knockAccepted": "আপনার যোগদানের অনুরোধ গৃহীত হয়েছে! আপনি এখন কোর্সে প্রবেশ করতে পারেন।",
|
||||
"sessionFull": "বিলম্ব! এই কার্যক্রম পূর্ণ।",
|
||||
"returnToCourse": "কোর্সে ফিরে যান",
|
||||
"returnHome": "বাড়িতে ফিরে যান",
|
||||
"@knockAccepted": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"@sessionFull": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"@returnToCourse": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"@returnHome": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
}
|
||||
}
|
||||
|
|
@ -3781,7 +3781,7 @@
|
|||
"joinPublicTrip": "མི་ཚེས་ལ་ལོག་འབད།",
|
||||
"startOwnTrip": "ངེད་རང་གི་ལོག་ལ་སྦྱོར་བཅོས།",
|
||||
"@@locale": "bo",
|
||||
"@@last_modified": "2026-02-24 14:59:46.294169",
|
||||
"@@last_modified": "2026-02-27 12:23:16.997026",
|
||||
"@alwaysUse24HourFormat": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
|
|
@ -10701,5 +10701,25 @@
|
|||
"@cannotJoinBannedRoom": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"knockAccepted": "Tua richiesta di ingresso è stata accettata! Ora puoi entrare nel corso.",
|
||||
"sessionFull": "Troppo tardi! Questa attività è piena.",
|
||||
"returnToCourse": "Torna al corso",
|
||||
"returnHome": "Torna a casa",
|
||||
"@knockAccepted": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"@sessionFull": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"@returnToCourse": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"@returnHome": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
{
|
||||
"@@last_modified": "2026-02-24 14:59:34.223579",
|
||||
"@@last_modified": "2026-02-27 12:22:46.690959",
|
||||
"about": "Quant a",
|
||||
"@about": {
|
||||
"type": "String",
|
||||
|
|
@ -11460,5 +11460,25 @@
|
|||
"@cannotJoinBannedRoom": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"knockAccepted": "La teva sol·licitud d'unió ha estat acceptada! Ara pots entrar al curs.",
|
||||
"sessionFull": " massa tard! Aquesta activitat està completa.",
|
||||
"returnToCourse": "Torna al curs",
|
||||
"returnHome": "Torna a casa",
|
||||
"@knockAccepted": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"@sessionFull": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"@returnToCourse": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"@returnHome": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"@@locale": "cs",
|
||||
"@@last_modified": "2026-02-24 14:59:31.949116",
|
||||
"@@last_modified": "2026-02-27 12:22:40.747640",
|
||||
"about": "O aplikaci",
|
||||
"@about": {
|
||||
"type": "String",
|
||||
|
|
@ -11872,5 +11872,25 @@
|
|||
"@cannotJoinBannedRoom": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"knockAccepted": "Vaše žádost o připojení byla přijata! Nyní můžete vstoupit do kurzu.",
|
||||
"sessionFull": "Příliš pozdě! Tato aktivita je plná.",
|
||||
"returnToCourse": "Vrátit se do kurzu",
|
||||
"returnHome": "Vrátit se domů",
|
||||
"@knockAccepted": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"@sessionFull": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"@returnToCourse": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"@returnHome": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
}
|
||||
}
|
||||
|
|
@ -1926,7 +1926,7 @@
|
|||
"playWithAI": "Leg med AI for nu",
|
||||
"courseStartDesc": "Pangea Bot er klar til at starte når som helst!\n\n...men læring er bedre med venner!",
|
||||
"@@locale": "da",
|
||||
"@@last_modified": "2026-02-24 14:59:17.793161",
|
||||
"@@last_modified": "2026-02-27 12:21:56.303075",
|
||||
"@aboutHomeserver": {
|
||||
"type": "String",
|
||||
"placeholders": {
|
||||
|
|
@ -12500,5 +12500,25 @@
|
|||
"@cannotJoinBannedRoom": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"knockAccepted": "Din anmodning om at deltage blev accepteret! Du kan nu deltage i kurset.",
|
||||
"sessionFull": "For sent! Denne aktivitet er fuld.",
|
||||
"returnToCourse": "Returner til kurset",
|
||||
"returnHome": "Returner hjem",
|
||||
"@knockAccepted": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"@sessionFull": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"@returnToCourse": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"@returnHome": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"@@locale": "de",
|
||||
"@@last_modified": "2026-02-24 14:59:28.164745",
|
||||
"@@last_modified": "2026-02-27 12:22:30.095800",
|
||||
"alwaysUse24HourFormat": "true",
|
||||
"@alwaysUse24HourFormat": {
|
||||
"description": "Set to true to always display time of day in 24 hour format."
|
||||
|
|
@ -11289,5 +11289,25 @@
|
|||
"@cannotJoinBannedRoom": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"knockAccepted": "Ihre Beitrittsanfrage wurde akzeptiert! Sie können jetzt am Kurs teilnehmen.",
|
||||
"sessionFull": "Zu spät! Diese Aktivität ist voll.",
|
||||
"returnToCourse": "Zum Kurs zurückkehren",
|
||||
"returnHome": "Nach Hause zurückkehren",
|
||||
"@knockAccepted": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"@sessionFull": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"@returnToCourse": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"@returnHome": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
}
|
||||
}
|
||||
|
|
@ -4476,7 +4476,7 @@
|
|||
"playWithAI": "Παίξτε με την Τεχνητή Νοημοσύνη προς το παρόν",
|
||||
"courseStartDesc": "Ο Pangea Bot είναι έτοιμος να ξεκινήσει οποιαδήποτε στιγμή!\n\n...αλλά η μάθηση είναι καλύτερη με φίλους!",
|
||||
"@@locale": "el",
|
||||
"@@last_modified": "2026-02-24 14:59:51.305832",
|
||||
"@@last_modified": "2026-02-27 12:23:28.161217",
|
||||
"@checkList": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
|
|
@ -12459,5 +12459,25 @@
|
|||
"@cannotJoinBannedRoom": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"knockAccepted": "Η αίτησή σας για συμμετοχή έγινε αποδεκτή! Μπορείτε τώρα να εισέλθετε στο μάθημα.",
|
||||
"sessionFull": "Πολύ αργά! Αυτή η δραστηριότητα είναι γεμάτη.",
|
||||
"returnToCourse": "Επιστροφή στο μάθημα",
|
||||
"returnHome": "Επιστροφή στην αρχική σελίδα",
|
||||
"@knockAccepted": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"@sessionFull": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"@returnToCourse": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"@returnHome": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
}
|
||||
}
|
||||
|
|
@ -5298,6 +5298,8 @@
|
|||
"languageUpdated": "Target language updated!",
|
||||
"voiceDropdownTitle": "Pangea Bot voice",
|
||||
"knockDesc": "Your request has been sent to course admin! You'll be let in if they approve.",
|
||||
"knockAccepted": "Your join request was accepted! You can now enter the course.",
|
||||
"@knockAccepted": {},
|
||||
"joinSpaceOnboardingDesc": "Do you have an invite code or link to a public course?",
|
||||
"welcomeUser": "Welcome {user}",
|
||||
"@welcomeUser": {
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
{
|
||||
"@@last_modified": "2026-02-24 14:59:53.981596",
|
||||
"@@last_modified": "2026-02-27 12:23:34.605003",
|
||||
"about": "Prio",
|
||||
"@about": {
|
||||
"type": "String",
|
||||
|
|
@ -12523,5 +12523,25 @@
|
|||
"@cannotJoinBannedRoom": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"knockAccepted": "Via aliĝpeticio estis akceptita! Vi nun povas eniri la kurson.",
|
||||
"sessionFull": "Tro malfrue! Ĉi tiu aktiveco estas plena.",
|
||||
"returnToCourse": "Reiri al la kurso",
|
||||
"returnHome": "Reiri hejmen",
|
||||
"@knockAccepted": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"@sessionFull": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"@returnToCourse": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"@returnHome": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"@@locale": "es",
|
||||
"@@last_modified": "2026-02-24 14:59:14.844768",
|
||||
"@@last_modified": "2026-02-27 12:21:50.256312",
|
||||
"about": "Acerca de",
|
||||
"@about": {
|
||||
"type": "String",
|
||||
|
|
@ -8610,5 +8610,25 @@
|
|||
"@cannotJoinBannedRoom": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"knockAccepted": "¡Tu solicitud de unión fue aceptada! Ahora puedes entrar al curso.",
|
||||
"sessionFull": "¡Demasiado tarde! Esta actividad está llena.",
|
||||
"returnToCourse": "Regresar al curso",
|
||||
"returnHome": "Regresar a casa",
|
||||
"@knockAccepted": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"@sessionFull": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"@returnToCourse": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"@returnHome": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"@@locale": "et",
|
||||
"@@last_modified": "2026-02-24 14:59:27.455277",
|
||||
"@@last_modified": "2026-02-27 12:22:27.939566",
|
||||
"about": "Rakenduse teave",
|
||||
"@about": {
|
||||
"type": "String",
|
||||
|
|
@ -11574,5 +11574,25 @@
|
|||
"@cannotJoinBannedRoom": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"knockAccepted": "Teie liitumissoovitus on vastu võetud! Nüüd saate kursusele siseneda.",
|
||||
"sessionFull": "Liiga hilja! See tegevus on täis.",
|
||||
"returnToCourse": "Tagasi kursusele",
|
||||
"returnHome": "Tagasi koju",
|
||||
"@knockAccepted": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"@sessionFull": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"@returnToCourse": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"@returnHome": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"@@locale": "eu",
|
||||
"@@last_modified": "2026-02-24 14:59:25.857325",
|
||||
"@@last_modified": "2026-02-27 12:22:24.260020",
|
||||
"about": "Honi buruz",
|
||||
"@about": {
|
||||
"type": "String",
|
||||
|
|
@ -11300,5 +11300,25 @@
|
|||
"@cannotJoinBannedRoom": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"knockAccepted": "Zure parte-hartze eskaera onartu da! Orain ikastaroan sartu zaitezke.",
|
||||
"sessionFull": "Berandu! Jarduera hau beteta dago.",
|
||||
"returnToCourse": "Itzuli ikastaroara",
|
||||
"returnHome": "Itzuli etxera",
|
||||
"@knockAccepted": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"@sessionFull": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"@returnToCourse": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"@returnHome": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
{
|
||||
"@@last_modified": "2026-02-24 14:59:48.860067",
|
||||
"@@last_modified": "2026-02-27 12:23:22.331158",
|
||||
"repeatPassword": "تکرار گذرواژه",
|
||||
"@repeatPassword": {},
|
||||
"about": "درباره",
|
||||
|
|
@ -11421,5 +11421,25 @@
|
|||
"@cannotJoinBannedRoom": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"knockAccepted": "درخواست پیوستن شما پذیرفته شد! اکنون میتوانید وارد دوره شوید.",
|
||||
"sessionFull": "خیلی دیر! این فعالیت پر است.",
|
||||
"returnToCourse": "بازگشت به دوره",
|
||||
"returnHome": "بازگشت به خانه",
|
||||
"@knockAccepted": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"@sessionFull": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"@returnToCourse": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"@returnHome": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
}
|
||||
}
|
||||
|
|
@ -4604,7 +4604,7 @@
|
|||
"playWithAI": "Leiki tekoälyn kanssa nyt",
|
||||
"courseStartDesc": "Pangea Bot on valmis milloin tahansa!\n\n...mutta oppiminen on parempaa ystävien kanssa!",
|
||||
"@@locale": "fi",
|
||||
"@@last_modified": "2026-02-24 14:59:16.651035",
|
||||
"@@last_modified": "2026-02-27 12:21:54.872687",
|
||||
"@notificationRuleJitsi": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
|
|
@ -11359,5 +11359,25 @@
|
|||
"@cannotJoinBannedRoom": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"knockAccepted": "Liittymispyyntösi hyväksyttiin! Voit nyt liittyä kurssille.",
|
||||
"sessionFull": "Liian myöhäistä! Tämä aktiviteetti on täynnä.",
|
||||
"returnToCourse": "Palaa kurssille",
|
||||
"returnHome": "Palaa kotiin",
|
||||
"@knockAccepted": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"@sessionFull": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"@returnToCourse": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"@returnHome": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
}
|
||||
}
|
||||
|
|
@ -2783,7 +2783,7 @@
|
|||
"selectAll": "Piliin lahat",
|
||||
"deselectAll": "Huwag piliin lahat",
|
||||
"@@locale": "fil",
|
||||
"@@last_modified": "2026-02-24 14:59:38.624762",
|
||||
"@@last_modified": "2026-02-27 12:22:55.704285",
|
||||
"@setCustomPermissionLevel": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
|
|
@ -12415,5 +12415,25 @@
|
|||
"@cannotJoinBannedRoom": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"knockAccepted": "Tinanggap ang iyong kahilingan na sumali! Maaari ka nang pumasok sa kurso.",
|
||||
"sessionFull": "Sobrang huli! Puno na ang aktibidad na ito.",
|
||||
"returnToCourse": "Bumalik sa kurso",
|
||||
"returnHome": "Bumalik sa bahay",
|
||||
"@knockAccepted": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"@sessionFull": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"@returnToCourse": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"@returnHome": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"@@locale": "fr",
|
||||
"@@last_modified": "2026-02-24 14:59:58.898976",
|
||||
"@@last_modified": "2026-02-27 12:23:45.139248",
|
||||
"about": "À propos",
|
||||
"@about": {
|
||||
"type": "String",
|
||||
|
|
@ -11706,5 +11706,25 @@
|
|||
"@cannotJoinBannedRoom": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"knockAccepted": "Votre demande de participation a été acceptée ! Vous pouvez maintenant entrer dans le cours.",
|
||||
"sessionFull": "Trop tard ! Cette activité est complète.",
|
||||
"returnToCourse": "Retour au cours",
|
||||
"returnHome": "Retour à l'accueil",
|
||||
"@knockAccepted": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"@sessionFull": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"@returnToCourse": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"@returnHome": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
}
|
||||
}
|
||||
|
|
@ -4639,7 +4639,7 @@
|
|||
"playWithAI": "Imir le AI faoi láthair",
|
||||
"courseStartDesc": "Tá Bot Pangea réidh chun dul am ar bith!\n\n...ach is fearr foghlaim le cairde!",
|
||||
"@@locale": "ga",
|
||||
"@@last_modified": "2026-02-24 14:59:58.265720",
|
||||
"@@last_modified": "2026-02-27 12:23:42.906285",
|
||||
"@writeAMessageLangCodes": {
|
||||
"type": "String",
|
||||
"placeholders": {
|
||||
|
|
@ -11311,5 +11311,25 @@
|
|||
"@cannotJoinBannedRoom": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"knockAccepted": "Glacadh le do hiarratas chun dul isteach! Is féidir leat anois dul isteach sa chúrsa.",
|
||||
"sessionFull": "Ró-dhéanach! Tá an gníomhaíocht seo lán.",
|
||||
"returnToCourse": "Téigh ar ais chuig an gcúrsa",
|
||||
"returnHome": "Téigh ar ais abhaile",
|
||||
"@knockAccepted": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"@sessionFull": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"@returnToCourse": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"@returnHome": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"@@locale": "gl",
|
||||
"@@last_modified": "2026-02-24 14:59:15.793089",
|
||||
"@@last_modified": "2026-02-27 12:21:52.398653",
|
||||
"about": "Acerca de",
|
||||
"@about": {
|
||||
"type": "String",
|
||||
|
|
@ -11300,5 +11300,25 @@
|
|||
"@cannotJoinBannedRoom": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"knockAccepted": "A túa solicitude de unión foi aceptada! Agora podes entrar no curso.",
|
||||
"sessionFull": "Demasiado tarde! Esta actividade está completa.",
|
||||
"returnToCourse": "Volver ao curso",
|
||||
"returnHome": "Volver a casa",
|
||||
"@knockAccepted": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"@sessionFull": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"@returnToCourse": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"@returnHome": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
{
|
||||
"@@last_modified": "2026-02-24 14:59:23.521541",
|
||||
"@@last_modified": "2026-02-27 12:22:17.196188",
|
||||
"about": "אודות",
|
||||
"@about": {
|
||||
"type": "String",
|
||||
|
|
@ -12483,5 +12483,25 @@
|
|||
"@cannotJoinBannedRoom": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"knockAccepted": "בקשת ההצטרפות שלך התקבלה! אתה יכול עכשיו להיכנס לקורס.",
|
||||
"sessionFull": "מאוחר מדי! הפעילות הזו מלאה.",
|
||||
"returnToCourse": "חזור לקורס",
|
||||
"returnHome": "חזור הביתה",
|
||||
"@knockAccepted": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"@sessionFull": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"@returnToCourse": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"@returnHome": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
}
|
||||
}
|
||||
|
|
@ -3999,7 +3999,7 @@
|
|||
"playWithAI": "अभी के लिए एआई के साथ खेलें",
|
||||
"courseStartDesc": "पैंजिया बॉट कभी भी जाने के लिए तैयार है!\n\n...लेकिन दोस्तों के साथ सीखना बेहतर है!",
|
||||
"@@locale": "hi",
|
||||
"@@last_modified": "2026-02-24 14:59:52.988187",
|
||||
"@@last_modified": "2026-02-27 12:23:32.751116",
|
||||
"@alwaysUse24HourFormat": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
|
|
@ -12047,5 +12047,25 @@
|
|||
"@cannotJoinBannedRoom": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"knockAccepted": "आपका शामिल होने का अनुरोध स्वीकार कर लिया गया है! आप अब पाठ्यक्रम में प्रवेश कर सकते हैं।",
|
||||
"sessionFull": "बहुत देर हो गई! यह गतिविधि पूरी हो चुकी है।",
|
||||
"returnToCourse": "पाठ्यक्रम पर लौटें",
|
||||
"returnHome": "घर पर लौटें",
|
||||
"@knockAccepted": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"@sessionFull": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"@returnToCourse": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"@returnHome": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"@@locale": "hr",
|
||||
"@@last_modified": "2026-02-24 14:59:22.923310",
|
||||
"@@last_modified": "2026-02-27 12:22:10.734115",
|
||||
"about": "Informacije",
|
||||
"@about": {
|
||||
"type": "String",
|
||||
|
|
@ -11793,5 +11793,25 @@
|
|||
"@cannotJoinBannedRoom": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"knockAccepted": "Vaš zahtjev za pridruživanje je prihvaćen! Sada možete ući u tečaj.",
|
||||
"sessionFull": "Prekasno! Ova aktivnost je puna.",
|
||||
"returnToCourse": "Vrati se na tečaj",
|
||||
"returnHome": "Vrati se kući",
|
||||
"@knockAccepted": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"@sessionFull": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"@returnToCourse": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"@returnHome": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"@@locale": "hu",
|
||||
"@@last_modified": "2026-02-24 14:59:18.503383",
|
||||
"@@last_modified": "2026-02-27 12:21:58.762966",
|
||||
"about": "Névjegy",
|
||||
"@about": {
|
||||
"type": "String",
|
||||
|
|
@ -11437,5 +11437,25 @@
|
|||
"@cannotJoinBannedRoom": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"knockAccepted": "A csatlakozási kérelmedet elfogadták! Most már beléphetsz a kurzusra.",
|
||||
"sessionFull": "Túl késő! Ez a tevékenység megtelt.",
|
||||
"returnToCourse": "Vissza a kurzushoz",
|
||||
"returnHome": "Vissza a főoldalra",
|
||||
"@knockAccepted": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"@sessionFull": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"@returnToCourse": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"@returnHome": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
}
|
||||
}
|
||||
|
|
@ -1954,7 +1954,7 @@
|
|||
"playWithAI": "Joca con le IA pro ora",
|
||||
"courseStartDesc": "Pangea Bot es preste a comenzar a qualunque momento!\n\n...ma apprender es melior con amicos!",
|
||||
"@@locale": "ia",
|
||||
"@@last_modified": "2026-02-24 14:59:24.082713",
|
||||
"@@last_modified": "2026-02-27 12:22:19.124682",
|
||||
"@alwaysUse24HourFormat": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
|
|
@ -12512,5 +12512,25 @@
|
|||
"@cannotJoinBannedRoom": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"knockAccepted": "Tua peticio de unione est acceptata! Tu nunc potes intrare in cursu.",
|
||||
"sessionFull": "Sero! Haec actio plena est.",
|
||||
"returnToCourse": "Redi ad cursum",
|
||||
"returnHome": "Redi domum",
|
||||
"@knockAccepted": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"@sessionFull": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"@returnToCourse": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"@returnHome": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
{
|
||||
"@@last_modified": "2026-02-24 14:59:19.093750",
|
||||
"@@last_modified": "2026-02-27 12:22:00.506173",
|
||||
"setAsCanonicalAlias": "Atur sebagai alias utama",
|
||||
"@setAsCanonicalAlias": {
|
||||
"type": "String",
|
||||
|
|
@ -11406,5 +11406,25 @@
|
|||
"@cannotJoinBannedRoom": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"knockAccepted": "Permintaan bergabung Anda diterima! Anda sekarang dapat memasuki kursus.",
|
||||
"sessionFull": "Terlambat! Aktivitas ini sudah penuh.",
|
||||
"returnToCourse": "Kembali ke kursus",
|
||||
"returnHome": "Kembali ke beranda",
|
||||
"@knockAccepted": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"@sessionFull": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"@returnToCourse": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"@returnHome": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
}
|
||||
}
|
||||
|
|
@ -4000,7 +4000,7 @@
|
|||
"playWithAI": "Joca con AI pro ora",
|
||||
"courseStartDesc": "Pangea Bot es preste a partir a qualunque momento!\n\n...ma apprender es melior con amicos!",
|
||||
"@@locale": "ie",
|
||||
"@@last_modified": "2026-02-24 14:59:21.774012",
|
||||
"@@last_modified": "2026-02-27 12:22:08.111962",
|
||||
"@alwaysUse24HourFormat": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
|
|
@ -12048,5 +12048,25 @@
|
|||
"@cannotJoinBannedRoom": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"knockAccepted": "Tú aontuigh an iarratas comhoibrithe! Is féidir leat anois dul isteach sa chúrsa.",
|
||||
"sessionFull": "Ró-dhéanach! Tá an gníomhaíocht seo lán.",
|
||||
"returnToCourse": "Téigh ar ais chuig an gcúrsa",
|
||||
"returnHome": "Téigh ar ais abhaile",
|
||||
"@knockAccepted": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"@sessionFull": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"@returnToCourse": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"@returnHome": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
{
|
||||
"@@last_modified": "2026-02-24 14:59:30.435614",
|
||||
"@@last_modified": "2026-02-27 12:22:35.902285",
|
||||
"about": "Informazioni",
|
||||
"@about": {
|
||||
"type": "String",
|
||||
|
|
@ -11394,5 +11394,25 @@
|
|||
"@cannotJoinBannedRoom": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"knockAccepted": "La tua richiesta di partecipazione è stata accettata! Puoi ora entrare nel corso.",
|
||||
"sessionFull": "Troppo tardi! Questa attività è piena.",
|
||||
"returnToCourse": "Torna al corso",
|
||||
"returnHome": "Torna a casa",
|
||||
"@knockAccepted": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"@sessionFull": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"@returnToCourse": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"@returnHome": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"@@locale": "ja",
|
||||
"@@last_modified": "2026-02-24 14:59:52.226358",
|
||||
"@@last_modified": "2026-02-27 12:23:30.414125",
|
||||
"about": "このアプリについて",
|
||||
"@about": {
|
||||
"type": "String",
|
||||
|
|
@ -12224,5 +12224,25 @@
|
|||
"@cannotJoinBannedRoom": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"knockAccepted": "あなたの参加リクエストが承認されました!これでコースに入ることができます。",
|
||||
"sessionFull": "遅すぎます!このアクティビティは満員です。",
|
||||
"returnToCourse": "コースに戻る",
|
||||
"returnHome": "ホームに戻る",
|
||||
"@knockAccepted": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"@sessionFull": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"@returnToCourse": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"@returnHome": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
}
|
||||
}
|
||||
|
|
@ -2590,7 +2590,7 @@
|
|||
"playWithAI": "ამ დროისთვის ითამაშეთ AI-თან",
|
||||
"courseStartDesc": "Pangea Bot მზადაა ნებისმიერ დროს გასასვლელად!\n\n...მაგრამ სწავლა უკეთესია მეგობრებთან ერთად!",
|
||||
"@@locale": "ka",
|
||||
"@@last_modified": "2026-02-24 14:59:56.446964",
|
||||
"@@last_modified": "2026-02-27 12:23:38.157366",
|
||||
"@alwaysUse24HourFormat": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
|
|
@ -12464,5 +12464,25 @@
|
|||
"@cannotJoinBannedRoom": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"knockAccepted": "თქვენი გაწვდილი მოთხოვნა მიღებულია! ახლა შეგიძლიათ კურსში გაწვდოთ.",
|
||||
"sessionFull": "მშვენიერი! ეს აქტივობა სავსეა.",
|
||||
"returnToCourse": "კურსში დაბრუნება",
|
||||
"returnHome": "მთავარ გვერდზე დაბრუნება",
|
||||
"@knockAccepted": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"@sessionFull": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"@returnToCourse": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"@returnHome": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
{
|
||||
"@@last_modified": "2026-02-24 14:59:13.645300",
|
||||
"@@last_modified": "2026-02-27 12:21:46.139305",
|
||||
"about": "소개",
|
||||
"@about": {
|
||||
"type": "String",
|
||||
|
|
@ -11526,5 +11526,25 @@
|
|||
"@cannotJoinBannedRoom": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"knockAccepted": "귀하의 참여 요청이 수락되었습니다! 이제 강의에 들어갈 수 있습니다.",
|
||||
"sessionFull": "너무 늦었습니다! 이 활동은 가득 찼습니다.",
|
||||
"returnToCourse": "강의로 돌아가기",
|
||||
"returnHome": "홈으로 돌아가기",
|
||||
"@knockAccepted": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"@sessionFull": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"@returnToCourse": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"@returnHome": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
}
|
||||
}
|
||||
|
|
@ -3857,7 +3857,7 @@
|
|||
"playWithAI": "Žaiskite su dirbtiniu intelektu dabar",
|
||||
"courseStartDesc": "Pangea botas pasiruošęs bet kada pradėti!\n\n...bet mokymasis yra geresnis su draugais!",
|
||||
"@@locale": "lt",
|
||||
"@@last_modified": "2026-02-24 14:59:43.333670",
|
||||
"@@last_modified": "2026-02-27 12:23:08.992471",
|
||||
"@alwaysUse24HourFormat": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
|
|
@ -12239,5 +12239,25 @@
|
|||
"@cannotJoinBannedRoom": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"knockAccepted": "Jūsų prisijungimo prašymas buvo priimtas! Dabar galite patekti į kursą.",
|
||||
"sessionFull": "Per vėlu! Ši veikla yra pilna.",
|
||||
"returnToCourse": "Grįžti į kursą",
|
||||
"returnHome": "Grįžti namo",
|
||||
"@knockAccepted": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"@sessionFull": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"@returnToCourse": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"@returnHome": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
}
|
||||
}
|
||||
|
|
@ -4605,7 +4605,7 @@
|
|||
"playWithAI": "Tagad spēlējiet ar AI",
|
||||
"courseStartDesc": "Pangea bots ir gatavs jebkurā laikā!\n\n...bet mācīties ir labāk ar draugiem!",
|
||||
"@@locale": "lv",
|
||||
"@@last_modified": "2026-02-24 14:59:39.254985",
|
||||
"@@last_modified": "2026-02-27 12:22:59.485868",
|
||||
"analyticsInactiveTitle": "Pieprasījumi neaktīviem lietotājiem nevar tikt nosūtīti",
|
||||
"analyticsInactiveDesc": "Neaktīvi lietotāji, kuri nav pieteikušies kopš šīs funkcijas ieviešanas, neredzēs jūsu pieprasījumu.\n\nPieprasījuma poga parādīsies, kad viņi atgriezīsies. Jūs varat atkārtoti nosūtīt pieprasījumu vēlāk, noklikšķinot uz pieprasījuma pogas viņu vārdā, kad tā būs pieejama.",
|
||||
"accessRequestedTitle": "Pieprasījums piekļūt analītikai",
|
||||
|
|
@ -11295,5 +11295,25 @@
|
|||
"@cannotJoinBannedRoom": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"knockAccepted": "Jūsu pievienošanās pieprasījums tika apstiprināts! Tagad varat piekļūt kursam.",
|
||||
"sessionFull": "Pārāk vēlu! Šī aktivitāte ir pilna.",
|
||||
"returnToCourse": "Atgriezties kursā",
|
||||
"returnHome": "Atgriezties mājās",
|
||||
"@knockAccepted": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"@sessionFull": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"@returnToCourse": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"@returnHome": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
{
|
||||
"@@last_modified": "2026-02-24 14:59:32.787654",
|
||||
"@@last_modified": "2026-02-27 12:22:42.590942",
|
||||
"about": "Om",
|
||||
"@about": {
|
||||
"type": "String",
|
||||
|
|
@ -11369,5 +11369,25 @@
|
|||
"@cannotJoinBannedRoom": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"knockAccepted": "Din forespørsel om å bli med ble akseptert! Du kan nå gå inn i kurset.",
|
||||
"sessionFull": "For sent! Denne aktiviteten er full.",
|
||||
"returnToCourse": "Gå tilbake til kurset",
|
||||
"returnHome": "Gå tilbake til hjem",
|
||||
"@knockAccepted": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"@sessionFull": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"@returnToCourse": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"@returnHome": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
{
|
||||
"@@last_modified": "2026-02-24 14:59:45.694424",
|
||||
"@@last_modified": "2026-02-27 12:23:15.467657",
|
||||
"about": "Over ons",
|
||||
"@about": {
|
||||
"type": "String",
|
||||
|
|
@ -11300,5 +11300,25 @@
|
|||
"@cannotJoinBannedRoom": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"knockAccepted": "Je aanvraag om deel te nemen is geaccepteerd! Je kunt nu de cursus binnenkomen.",
|
||||
"sessionFull": "Te laat! Deze activiteit is vol.",
|
||||
"returnToCourse": "Terug naar cursus",
|
||||
"returnHome": "Terug naar huis",
|
||||
"@knockAccepted": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"@sessionFull": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"@returnToCourse": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"@returnHome": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"@@locale": "pl",
|
||||
"@@last_modified": "2026-02-24 14:59:49.576687",
|
||||
"@@last_modified": "2026-02-27 12:23:24.344739",
|
||||
"about": "O aplikacji",
|
||||
"@about": {
|
||||
"type": "String",
|
||||
|
|
@ -11422,5 +11422,25 @@
|
|||
"@cannotJoinBannedRoom": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"knockAccepted": "Twoja prośba o dołączenie została zaakceptowana! Możesz teraz wejść na kurs.",
|
||||
"sessionFull": "Za późno! Ta aktywność jest pełna.",
|
||||
"returnToCourse": "Powrót do kursu",
|
||||
"returnHome": "Powrót do domu",
|
||||
"@knockAccepted": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"@sessionFull": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"@returnToCourse": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"@returnHome": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
{
|
||||
"@@last_modified": "2026-02-24 14:59:26.541602",
|
||||
"@@last_modified": "2026-02-27 12:22:26.128852",
|
||||
"copiedToClipboard": "Copiada para a área de transferência",
|
||||
"@copiedToClipboard": {
|
||||
"type": "String",
|
||||
|
|
@ -12521,5 +12521,25 @@
|
|||
"@cannotJoinBannedRoom": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"knockAccepted": "Seu pedido de entrada foi aceito! Você pode agora entrar no curso.",
|
||||
"sessionFull": "Muito tarde! Esta atividade está cheia.",
|
||||
"returnToCourse": "Voltar ao curso",
|
||||
"returnHome": "Voltar para casa",
|
||||
"@knockAccepted": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"@sessionFull": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"@returnToCourse": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"@returnHome": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
{
|
||||
"@@last_modified": "2026-02-24 14:59:25.036168",
|
||||
"@@last_modified": "2026-02-27 12:22:21.502851",
|
||||
"about": "Sobre",
|
||||
"@about": {
|
||||
"type": "String",
|
||||
|
|
@ -11300,5 +11300,25 @@
|
|||
"@cannotJoinBannedRoom": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"knockAccepted": "Seu pedido de entrada foi aceito! Você pode agora entrar no curso.",
|
||||
"sessionFull": "Muito tarde! Esta atividade está cheia.",
|
||||
"returnToCourse": "Voltar ao curso",
|
||||
"returnHome": "Voltar para casa",
|
||||
"@knockAccepted": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"@sessionFull": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"@returnToCourse": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"@returnHome": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
}
|
||||
}
|
||||
|
|
@ -3327,7 +3327,7 @@
|
|||
"selectAll": "Selecionar tudo",
|
||||
"deselectAll": "Desmarcar tudo",
|
||||
"@@locale": "pt_PT",
|
||||
"@@last_modified": "2026-02-24 14:59:36.068751",
|
||||
"@@last_modified": "2026-02-27 12:22:50.283963",
|
||||
"@alwaysUse24HourFormat": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
|
|
@ -12468,5 +12468,25 @@
|
|||
"@cannotJoinBannedRoom": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"knockAccepted": "Seu pedido de entrada foi aceito! Você pode agora entrar no curso.",
|
||||
"sessionFull": "Muito tarde! Esta atividade está cheia.",
|
||||
"returnToCourse": "Voltar ao curso",
|
||||
"returnHome": "Voltar para casa",
|
||||
"@knockAccepted": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"@sessionFull": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"@returnToCourse": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"@returnHome": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
{
|
||||
"@@last_modified": "2026-02-24 14:59:20.082703",
|
||||
"@@last_modified": "2026-02-27 12:22:02.332584",
|
||||
"about": "Despre",
|
||||
"@about": {
|
||||
"type": "String",
|
||||
|
|
@ -12169,5 +12169,25 @@
|
|||
"@cannotJoinBannedRoom": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"knockAccepted": "Cererea ta de aderare a fost acceptată! Acum poți intra în curs.",
|
||||
"sessionFull": "Prea târziu! Această activitate este plină.",
|
||||
"returnToCourse": "Întoarce-te la curs",
|
||||
"returnHome": "Întoarce-te acasă",
|
||||
"@knockAccepted": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"@sessionFull": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"@returnToCourse": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"@returnHome": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"@@locale": "ru",
|
||||
"@@last_modified": "2026-02-24 14:59:55.748204",
|
||||
"@@last_modified": "2026-02-27 12:23:35.945707",
|
||||
"about": "О проекте",
|
||||
"@about": {
|
||||
"type": "String",
|
||||
|
|
@ -11300,5 +11300,25 @@
|
|||
"@cannotJoinBannedRoom": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"knockAccepted": "Ваш запрос на присоединение был принят! Теперь вы можете войти в курс.",
|
||||
"sessionFull": "Слишком поздно! Это занятие заполнено.",
|
||||
"returnToCourse": "Вернуться к курсу",
|
||||
"returnHome": "Вернуться на главную",
|
||||
"@knockAccepted": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"@sessionFull": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"@returnToCourse": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"@returnHome": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"@@locale": "sk",
|
||||
"@@last_modified": "2026-02-24 14:59:21.085850",
|
||||
"@@last_modified": "2026-02-27 12:22:05.630664",
|
||||
"about": "O aplikácii",
|
||||
"@about": {
|
||||
"type": "String",
|
||||
|
|
@ -12518,5 +12518,25 @@
|
|||
"@cannotJoinBannedRoom": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"knockAccepted": "Vaša žiadosť o pripojenie bola prijatá! Teraz môžete vstúpiť do kurzu.",
|
||||
"sessionFull": "Príliš neskoro! Táto aktivita je plná.",
|
||||
"returnToCourse": "Vrátiť sa do kurzu",
|
||||
"returnHome": "Vrátiť sa domov",
|
||||
"@knockAccepted": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"@sessionFull": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"@returnToCourse": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"@returnHome": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
}
|
||||
}
|
||||
|
|
@ -2460,7 +2460,7 @@
|
|||
"playWithAI": "Za zdaj igrajte z AI-jem",
|
||||
"courseStartDesc": "Pangea Bot je pripravljen kadarkoli!\n\n...ampak je bolje učiti se s prijatelji!",
|
||||
"@@locale": "sl",
|
||||
"@@last_modified": "2026-02-24 14:59:28.892881",
|
||||
"@@last_modified": "2026-02-27 12:22:32.222833",
|
||||
"@alwaysUse24HourFormat": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
|
|
@ -12515,5 +12515,25 @@
|
|||
"@cannotJoinBannedRoom": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"knockAccepted": "Vaša prošnja za vstop je bila sprejeta! Zdaj lahko vstopite v tečaj.",
|
||||
"sessionFull": "Prepozno! Ta aktivnost je polna.",
|
||||
"returnToCourse": "Vrni se v tečaj",
|
||||
"returnHome": "Vrni se domov",
|
||||
"@knockAccepted": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"@sessionFull": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"@returnToCourse": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"@returnHome": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
{
|
||||
"@@last_modified": "2026-02-24 14:59:57.459309",
|
||||
"@@last_modified": "2026-02-27 12:23:40.141969",
|
||||
"about": "О програму",
|
||||
"@about": {
|
||||
"type": "String",
|
||||
|
|
@ -12530,5 +12530,25 @@
|
|||
"@cannotJoinBannedRoom": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"knockAccepted": "Vaš zahtev za pridruživanje je prihvaćen! Sada možete ući u kurs.",
|
||||
"sessionFull": "Prekasno! Ova aktivnost je puna.",
|
||||
"returnToCourse": "Vrati se na kurs",
|
||||
"returnHome": "Vrati se kući",
|
||||
"@knockAccepted": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"@sessionFull": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"@returnToCourse": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"@returnHome": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
{
|
||||
"@@last_modified": "2026-02-24 14:59:50.399849",
|
||||
"@@last_modified": "2026-02-27 12:23:26.112167",
|
||||
"about": "Om",
|
||||
"@about": {
|
||||
"type": "String",
|
||||
|
|
@ -11912,5 +11912,25 @@
|
|||
"@cannotJoinBannedRoom": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"knockAccepted": "Din ansökan om att gå med har accepterats! Du kan nu gå in i kursen.",
|
||||
"sessionFull": "För sent! Denna aktivitet är full.",
|
||||
"returnToCourse": "Återvänd till kursen",
|
||||
"returnHome": "Återvänd hem",
|
||||
"@knockAccepted": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"@sessionFull": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"@returnToCourse": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"@returnHome": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
{
|
||||
"@@last_modified": "2026-02-24 14:59:45.075838",
|
||||
"@@last_modified": "2026-02-27 12:23:13.510421",
|
||||
"acceptedTheInvitation": "👍 {username} அழைப்பை ஏற்றுக்கொண்டது",
|
||||
"@acceptedTheInvitation": {
|
||||
"type": "String",
|
||||
|
|
@ -11420,5 +11420,25 @@
|
|||
"@cannotJoinBannedRoom": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"knockAccepted": "உங்கள் சேர்க்கை கோரிக்கை ஏற்கப்பட்டது! நீங்கள் இப்போது பாடத்தில் நுழையலாம்.",
|
||||
"sessionFull": "மிகவும் தாமதமாக! இந்த செயல்பாடு நிரம்பியுள்ளது.",
|
||||
"returnToCourse": "பாடத்திற்கு திரும்பவும்",
|
||||
"returnHome": "வீட்டிற்கு திரும்பவும்",
|
||||
"@knockAccepted": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"@sessionFull": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"@returnToCourse": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"@returnHome": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
}
|
||||
}
|
||||
|
|
@ -1916,7 +1916,7 @@
|
|||
"playWithAI": "ఇప్పుడే AI తో ఆడండి",
|
||||
"courseStartDesc": "పాంజియా బాట్ ఎప్పుడైనా సిద్ధంగా ఉంటుంది!\n\n...కానీ స్నేహితులతో నేర్చుకోవడం మెరుగైనది!",
|
||||
"@@locale": "te",
|
||||
"@@last_modified": "2026-02-24 14:59:42.206776",
|
||||
"@@last_modified": "2026-02-27 12:23:05.628176",
|
||||
"@setCustomPermissionLevel": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
|
|
@ -12523,5 +12523,25 @@
|
|||
"@cannotJoinBannedRoom": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"knockAccepted": "మీ చేరిక అభ్యర్థన ఆమోదించబడింది! మీరు ఇప్పుడు కోర్సులో ప్రవేశించవచ్చు.",
|
||||
"sessionFull": "చాలా ఆలస్యమైంది! ఈ కార్యకలాపం నిండిపోయింది.",
|
||||
"returnToCourse": "కోర్సుకు తిరిగి వెళ్ళండి",
|
||||
"returnHome": "ఇంటికి తిరిగి వెళ్ళండి",
|
||||
"@knockAccepted": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"@sessionFull": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"@returnToCourse": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"@returnHome": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
}
|
||||
}
|
||||
|
|
@ -3999,7 +3999,7 @@
|
|||
"playWithAI": "เล่นกับ AI ชั่วคราว",
|
||||
"courseStartDesc": "Pangea Bot พร้อมที่จะเริ่มต้นได้ทุกเมื่อ!\n\n...แต่การเรียนรู้ดีกว่ากับเพื่อน!",
|
||||
"@@locale": "th",
|
||||
"@@last_modified": "2026-02-24 14:59:35.264857",
|
||||
"@@last_modified": "2026-02-27 12:22:48.494331",
|
||||
"@alwaysUse24HourFormat": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
|
|
@ -12047,5 +12047,25 @@
|
|||
"@cannotJoinBannedRoom": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"knockAccepted": "คำขอเข้าร่วมของคุณได้รับการอนุมัติแล้ว! ตอนนี้คุณสามารถเข้าร่วมหลักสูตรได้แล้ว",
|
||||
"sessionFull": "สายเกินไป! กิจกรรมนี้เต็มแล้ว",
|
||||
"returnToCourse": "กลับไปยังหลักสูตร",
|
||||
"returnHome": "กลับไปยังหน้าแรก",
|
||||
"@knockAccepted": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"@sessionFull": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"@returnToCourse": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"@returnHome": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"@@locale": "tr",
|
||||
"@@last_modified": "2026-02-24 14:59:41.166599",
|
||||
"@@last_modified": "2026-02-27 12:23:03.199696",
|
||||
"about": "Hakkında",
|
||||
"@about": {
|
||||
"type": "String",
|
||||
|
|
@ -11640,5 +11640,25 @@
|
|||
"@cannotJoinBannedRoom": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"knockAccepted": "Katılma isteğiniz kabul edildi! Artık derse girebilirsiniz.",
|
||||
"sessionFull": "Geç kaldınız! Bu etkinlik dolu.",
|
||||
"returnToCourse": "Derse geri dön",
|
||||
"returnHome": "Ana sayfaya geri dön",
|
||||
"@knockAccepted": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"@sessionFull": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"@returnToCourse": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"@returnHome": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"@@locale": "uk",
|
||||
"@@last_modified": "2026-02-24 14:59:31.151553",
|
||||
"@@last_modified": "2026-02-27 12:22:38.290443",
|
||||
"about": "Про застосунок",
|
||||
"@about": {
|
||||
"type": "String",
|
||||
|
|
@ -11300,5 +11300,25 @@
|
|||
"@cannotJoinBannedRoom": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"knockAccepted": "Ваш запит на приєднання був прийнятий! Тепер ви можете увійти до курсу.",
|
||||
"sessionFull": "Занадто пізно! Ця активність заповнена.",
|
||||
"returnToCourse": "Повернутися до курсу",
|
||||
"returnHome": "Повернутися додому",
|
||||
"@knockAccepted": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"@sessionFull": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"@returnToCourse": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"@returnHome": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
}
|
||||
}
|
||||
|
|
@ -3495,7 +3495,7 @@
|
|||
"setupChatBackup": "Chat zaxirasini sozlash",
|
||||
"@setupChatBackup": {},
|
||||
"@@locale": "uz",
|
||||
"@@last_modified": "2026-02-24 14:59:37.791267",
|
||||
"@@last_modified": "2026-02-27 12:22:53.595075",
|
||||
"noMoreResultsFound": "Boshqa natijalar topilmadi",
|
||||
"chatSearchedUntil": "Chat {time} gacha qidirildi",
|
||||
"federationBaseUrl": "Federatsiya Asos URL",
|
||||
|
|
@ -11194,5 +11194,25 @@
|
|||
"@cannotJoinBannedRoom": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"knockAccepted": "Sizning qo'shilish so'rovingiz qabul qilindi! Endi kursga kirishingiz mumkin.",
|
||||
"sessionFull": "Kecha! Ushbu faoliyat to'la.",
|
||||
"returnToCourse": "Kursga qaytish",
|
||||
"returnHome": "Boshqaruvga qaytish",
|
||||
"@knockAccepted": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"@sessionFull": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"@returnToCourse": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"@returnHome": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
{
|
||||
"@@last_modified": "2026-02-24 14:59:44.047251",
|
||||
"@@last_modified": "2026-02-27 12:23:11.360379",
|
||||
"about": "Giới thiệu",
|
||||
"@about": {
|
||||
"type": "String",
|
||||
|
|
@ -7017,5 +7017,25 @@
|
|||
"@cannotJoinBannedRoom": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"knockAccepted": "Yêu cầu tham gia của bạn đã được chấp nhận! Bạn có thể vào khóa học ngay bây giờ.",
|
||||
"sessionFull": "Quá muộn! Hoạt động này đã đầy.",
|
||||
"returnToCourse": "Quay lại khóa học",
|
||||
"returnHome": "Quay về trang chủ",
|
||||
"@knockAccepted": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"@sessionFull": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"@returnToCourse": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"@returnHome": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
}
|
||||
}
|
||||
|
|
@ -1852,7 +1852,7 @@
|
|||
"selectAll": "全選",
|
||||
"deselectAll": "取消全選",
|
||||
"@@locale": "yue",
|
||||
"@@last_modified": "2026-02-24 14:59:29.472390",
|
||||
"@@last_modified": "2026-02-27 12:22:34.197254",
|
||||
"@ignoreUser": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
|
|
@ -12530,5 +12530,25 @@
|
|||
"@cannotJoinBannedRoom": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"knockAccepted": "你的加入請求已被接受!你現在可以進入課程。",
|
||||
"sessionFull": "太遲了!這個活動已滿。",
|
||||
"returnToCourse": "返回課程",
|
||||
"returnHome": "返回主頁",
|
||||
"@knockAccepted": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"@sessionFull": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"@returnToCourse": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"@returnHome": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"@@locale": "zh",
|
||||
"@@last_modified": "2026-02-24 14:59:46.957191",
|
||||
"@@last_modified": "2026-02-27 12:23:18.517226",
|
||||
"about": "关于",
|
||||
"@about": {
|
||||
"type": "String",
|
||||
|
|
@ -11300,5 +11300,25 @@
|
|||
"@cannotJoinBannedRoom": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"knockAccepted": "您的加入请求已被接受!您现在可以进入课程。",
|
||||
"sessionFull": "太晚了!此活动已满。",
|
||||
"returnToCourse": "返回课程",
|
||||
"returnHome": "返回首页",
|
||||
"@knockAccepted": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"@sessionFull": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"@returnToCourse": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"@returnHome": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
{
|
||||
"@@last_modified": "2026-02-24 14:59:36.723188",
|
||||
"@@last_modified": "2026-02-27 12:22:51.603823",
|
||||
"about": "關於",
|
||||
"@about": {
|
||||
"type": "String",
|
||||
|
|
@ -11439,5 +11439,25 @@
|
|||
"@cannotJoinBannedRoom": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"knockAccepted": "您的加入請求已被接受!您現在可以進入課程。",
|
||||
"sessionFull": "太遲了!此活動已滿。",
|
||||
"returnToCourse": "返回課程",
|
||||
"returnHome": "返回首頁",
|
||||
"@knockAccepted": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"@sessionFull": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"@returnToCourse": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"@returnHome": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
}
|
||||
}
|
||||
|
|
@ -14,12 +14,10 @@ import 'package:fluffychat/l10n/l10n.dart';
|
|||
import 'package:fluffychat/pages/chat_list/chat_list_view.dart';
|
||||
import 'package:fluffychat/pangea/chat_list/utils/app_version_util.dart';
|
||||
import 'package:fluffychat/pangea/chat_list/utils/chat_list_handle_space_tap.dart';
|
||||
import 'package:fluffychat/pangea/chat_settings/constants/pangea_room_types.dart';
|
||||
import 'package:fluffychat/pangea/chat_settings/widgets/chat_context_menu_action.dart';
|
||||
import 'package:fluffychat/pangea/common/utils/error_handler.dart';
|
||||
import 'package:fluffychat/pangea/extensions/pangea_room_extension.dart';
|
||||
import 'package:fluffychat/pangea/join_codes/knock_room_extension.dart';
|
||||
import 'package:fluffychat/pangea/join_codes/knock_tracker.dart';
|
||||
import 'package:fluffychat/pangea/join_codes/space_code_controller.dart';
|
||||
import 'package:fluffychat/pangea/join_codes/space_code_repo.dart';
|
||||
import 'package:fluffychat/pangea/navigation/navigation_util.dart';
|
||||
|
|
@ -539,46 +537,7 @@ class ChatListController extends State<ChatList>
|
|||
//#Pangea
|
||||
_invitedSpaceSubscription = Matrix.of(context).client.onSync.stream
|
||||
.where((event) => event.rooms?.invite != null)
|
||||
.listen((event) async {
|
||||
for (final inviteEntry in event.rooms!.invite!.entries) {
|
||||
if (inviteEntry.value.inviteState == null) continue;
|
||||
final isSpace = inviteEntry.value.inviteState!.any(
|
||||
(event) =>
|
||||
event.type == EventTypes.RoomCreate &&
|
||||
event.content['type'] == 'm.space',
|
||||
);
|
||||
final isAnalytics = inviteEntry.value.inviteState!.any(
|
||||
(event) =>
|
||||
event.type == EventTypes.RoomCreate &&
|
||||
event.content['type'] == PangeaRoomTypes.analytics,
|
||||
);
|
||||
final hasKnocked = KnockTracker.hasKnocked(
|
||||
Matrix.of(context).client,
|
||||
inviteEntry.key,
|
||||
);
|
||||
|
||||
final room = Matrix.of(context).client.getRoomById(inviteEntry.key);
|
||||
if (room == null) continue;
|
||||
if (isAnalytics || hasKnocked) {
|
||||
try {
|
||||
await room.joinKnockedRoom();
|
||||
} catch (err, s) {
|
||||
ErrorHandler.logError(
|
||||
m: "Failed to join analytics room",
|
||||
e: err,
|
||||
s: s,
|
||||
data: {"roomId": room.id},
|
||||
);
|
||||
}
|
||||
} else if (isSpace) {
|
||||
if (room.classCode?.toLowerCase() ==
|
||||
SpaceCodeRepo.recentCode?.toLowerCase()) {
|
||||
continue;
|
||||
}
|
||||
chatListHandleSpaceTap(context, room);
|
||||
}
|
||||
}
|
||||
});
|
||||
.listen(_onInviteSync);
|
||||
|
||||
MatrixState.pangeaController.subscriptionController.subscriptionNotifier
|
||||
.addListener(_onSubscribe);
|
||||
|
|
@ -627,7 +586,7 @@ class ChatListController extends State<ChatList>
|
|||
});
|
||||
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
_joinInvitedRooms();
|
||||
_joinInvitedSpaces();
|
||||
});
|
||||
// Pangea#
|
||||
|
||||
|
|
@ -639,37 +598,53 @@ class ChatListController extends State<ChatList>
|
|||
if (mounted) showSubscribedSnackbar(context);
|
||||
}
|
||||
|
||||
Future<void> _joinInvitedRooms() async {
|
||||
final invitedRooms = Matrix.of(
|
||||
Future<void> _joinInvitedSpaces() async {
|
||||
final invitedSpaces = Matrix.of(
|
||||
context,
|
||||
).client.rooms.where((r) => r.membership == Membership.invite);
|
||||
).client.rooms.where((r) => r.isSpace && r.membership == Membership.invite);
|
||||
|
||||
final spaces = [];
|
||||
for (final room in invitedRooms) {
|
||||
final hasKnocked = KnockTracker.hasKnocked(
|
||||
Matrix.of(context).client,
|
||||
room.id,
|
||||
);
|
||||
for (final space in invitedSpaces) {
|
||||
await showInviteDialog(space, context);
|
||||
}
|
||||
}
|
||||
|
||||
if (hasKnocked || room.isAnalyticsRoom) {
|
||||
Future<void> _onInviteSync(SyncUpdate update) async {
|
||||
final roomIds =
|
||||
update.rooms?.invite?.entries.map((e) => e.key).toSet() ?? {};
|
||||
|
||||
for (final roomId in roomIds) {
|
||||
final room = Matrix.of(context).client.getRoomById(roomId);
|
||||
if (room == null) continue;
|
||||
final isSpace = room.isSpace;
|
||||
final isAnalytics = room.isAnalyticsRoom;
|
||||
if (!isSpace && !isAnalytics) continue;
|
||||
|
||||
final hasKnocked = room.hasKnocked;
|
||||
|
||||
// Auto-join analytics rooms or spaces the user has knocked on
|
||||
if (isAnalytics || hasKnocked) {
|
||||
try {
|
||||
await room.joinKnockedRoom();
|
||||
} catch (err, s) {
|
||||
ErrorHandler.logError(
|
||||
m: "Failed to join knocked room",
|
||||
m: "Failed to join analytics room",
|
||||
e: err,
|
||||
s: s,
|
||||
data: {"roomId": room.id},
|
||||
);
|
||||
}
|
||||
return;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (room.isSpace) spaces.add(room);
|
||||
}
|
||||
if (isSpace) {
|
||||
// If user joined via code, don't show invite popup
|
||||
final roomCode = room.classCode?.toLowerCase();
|
||||
final cachedCode = SpaceCodeRepo.recentCode?.toLowerCase();
|
||||
if (cachedCode == roomCode) continue;
|
||||
|
||||
for (final space in spaces) {
|
||||
await showInviteDialog(space, context);
|
||||
// Show invite popup
|
||||
chatListHandleSpaceTap(context, room);
|
||||
}
|
||||
}
|
||||
}
|
||||
// Pangea#
|
||||
|
|
|
|||
|
|
@ -36,4 +36,6 @@ class PangeaEventTypes {
|
|||
static const analyticsSettings = "pangea.analytics_settings";
|
||||
|
||||
static const regenerationRequest = "pangea.regeneration_request";
|
||||
|
||||
static const knockedRooms = 'org.pangea.knocked_rooms';
|
||||
}
|
||||
|
|
|
|||
36
lib/pangea/join_codes/knock_notification_utils.dart
Normal file
36
lib/pangea/join_codes/knock_notification_utils.dart
Normal file
|
|
@ -0,0 +1,36 @@
|
|||
import 'package:matrix/matrix.dart';
|
||||
|
||||
import 'package:fluffychat/pangea/join_codes/knock_room_extension.dart';
|
||||
|
||||
/// Returns true when a push notification event is an accepted-knock invite —
|
||||
/// i.e. an m.room.member invite targeting the current user in a room the user
|
||||
/// previously knocked on.
|
||||
///
|
||||
/// Extracted as a pure function so it can be unit-tested without a Flutter
|
||||
/// environment or a live Matrix client.
|
||||
bool isKnockAcceptedInvite({
|
||||
required String eventType,
|
||||
required String? newMembership,
|
||||
required String? stateKey,
|
||||
required String? currentUserId,
|
||||
required bool hasKnocked,
|
||||
}) {
|
||||
return eventType == EventTypes.RoomMember &&
|
||||
newMembership == 'invite' &&
|
||||
stateKey == currentUserId &&
|
||||
hasKnocked;
|
||||
}
|
||||
|
||||
/// Convenience wrapper that reads [KnockTracker] state from a live [Client].
|
||||
bool isKnockAcceptedInviteForClient({
|
||||
required Event event,
|
||||
required Client client,
|
||||
}) {
|
||||
return isKnockAcceptedInvite(
|
||||
eventType: event.type,
|
||||
newMembership: event.content.tryGet<String>('membership'),
|
||||
stateKey: event.stateKey,
|
||||
currentUserId: client.userID,
|
||||
hasKnocked: client.hasEverKnockedRoom(event.room.id),
|
||||
);
|
||||
}
|
||||
|
|
@ -1,24 +1,92 @@
|
|||
import 'package:matrix/matrix.dart';
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:fluffychat/pangea/join_codes/knock_tracker.dart';
|
||||
import 'package:matrix/matrix.dart';
|
||||
import 'package:sentry_flutter/sentry_flutter.dart';
|
||||
|
||||
import 'package:fluffychat/pangea/common/utils/error_handler.dart';
|
||||
import 'package:fluffychat/pangea/events/constants/pangea_event_types.dart';
|
||||
import 'package:fluffychat/pangea/join_codes/knocked_rooms_model.dart';
|
||||
|
||||
extension KnockRoomExtension on Room {
|
||||
bool get hasKnocked => KnockTracker.hasKnocked(client, id);
|
||||
bool get hasKnocked => client.hasKnockedRoom(id);
|
||||
|
||||
Future<void> joinKnockedRoom() async {
|
||||
await join();
|
||||
await KnockTracker.clearKnock(client, id);
|
||||
await client.onJoinKnockedRoom(id);
|
||||
}
|
||||
}
|
||||
|
||||
extension KnockClientExtension on Client {
|
||||
KnockedRoomsModel get _knockedRooms {
|
||||
final data = accountData[PangeaEventTypes.knockedRooms];
|
||||
if (data != null) {
|
||||
return KnockedRoomsModel.fromJson(data.content);
|
||||
}
|
||||
return const KnockedRoomsModel();
|
||||
}
|
||||
|
||||
Future<void> _setKnockedRooms(KnockedRoomsModel model) async {
|
||||
final prevModel = _knockedRooms;
|
||||
if (model == prevModel) {
|
||||
Logs().w('Knocked rooms model is the same as previous, skipping write.');
|
||||
Logs().w('Model: ${model.toJson()}');
|
||||
Logs().w('Previous Model: ${prevModel.toJson()}');
|
||||
return;
|
||||
}
|
||||
|
||||
await setAccountData(
|
||||
userID!,
|
||||
PangeaEventTypes.knockedRooms,
|
||||
model.toJson(),
|
||||
);
|
||||
|
||||
final updatedModel = _knockedRooms;
|
||||
if (model == updatedModel) {
|
||||
try {
|
||||
await onSync.stream
|
||||
.firstWhere((sync) => sync.accountData != null)
|
||||
.timeout(Duration(seconds: 10));
|
||||
} catch (e, s) {
|
||||
ErrorHandler.logError(
|
||||
e: e,
|
||||
s: s,
|
||||
data: {
|
||||
'client_user_id': userID,
|
||||
'expected_ids': model.toJson(),
|
||||
'updated_ids': updatedModel.toJson(),
|
||||
},
|
||||
level: e is TimeoutException
|
||||
? SentryLevel.warning
|
||||
: SentryLevel.error,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Future<String> knockAndRecordRoom(
|
||||
String roomIdOrAlias, {
|
||||
List<String>? via,
|
||||
String? reason,
|
||||
}) async {
|
||||
final resp = await knockRoom(roomIdOrAlias, via: via, reason: reason);
|
||||
await KnockTracker.recordKnock(this, roomIdOrAlias);
|
||||
final updatedModel = _knockedRooms.copyWithKnockedRoom(roomIdOrAlias);
|
||||
await _setKnockedRooms(updatedModel);
|
||||
return resp;
|
||||
}
|
||||
|
||||
Future<void> onJoinKnockedRoom(String roomId) async {
|
||||
final updatedModel = _knockedRooms.copyWithAcceptedInviteRoom(roomId);
|
||||
await _setKnockedRooms(updatedModel);
|
||||
}
|
||||
|
||||
bool hasKnockedRoom(String roomId) {
|
||||
return _knockedRooms.knockedRoomIds.contains(roomId);
|
||||
}
|
||||
|
||||
bool hasEverKnockedRoom(String roomId) {
|
||||
return hasKnockedRoom(roomId) ||
|
||||
_knockedRooms.acceptedInviteRoomIds.contains(roomId);
|
||||
}
|
||||
|
||||
List<String> get knockedRoomIds => _knockedRooms.knockedRoomIds;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,45 +0,0 @@
|
|||
import 'package:matrix/matrix.dart';
|
||||
|
||||
/// Tracks room IDs the user has knocked on so the client can auto-accept
|
||||
/// invites for previously knocked rooms.
|
||||
///
|
||||
/// Stored in Matrix account data under [_accountDataKey] so the state
|
||||
/// survives reinstall, logout, and syncs across devices.
|
||||
class KnockTracker {
|
||||
static const String _accountDataKey = 'org.pangea.knocked_rooms';
|
||||
static const String _roomIdsField = 'room_ids';
|
||||
|
||||
static Future<void> recordKnock(Client client, String roomId) async {
|
||||
final ids = _getKnockedRoomIds(client);
|
||||
if (!ids.contains(roomId)) {
|
||||
ids.add(roomId);
|
||||
await _writeIds(client, ids);
|
||||
}
|
||||
}
|
||||
|
||||
static bool hasKnocked(Client client, String roomId) {
|
||||
return _getKnockedRoomIds(client).contains(roomId);
|
||||
}
|
||||
|
||||
static Future<void> clearKnock(Client client, String roomId) async {
|
||||
final ids = _getKnockedRoomIds(client);
|
||||
if (ids.remove(roomId)) {
|
||||
await _writeIds(client, ids);
|
||||
}
|
||||
}
|
||||
|
||||
static List<String> _getKnockedRoomIds(Client client) {
|
||||
final data = client.accountData[_accountDataKey];
|
||||
final list = data?.content[_roomIdsField];
|
||||
if (list is List) {
|
||||
return list.cast<String>().toList();
|
||||
}
|
||||
return [];
|
||||
}
|
||||
|
||||
static Future<void> _writeIds(Client client, List<String> ids) async {
|
||||
await client.setAccountData(client.userID!, _accountDataKey, {
|
||||
_roomIdsField: ids,
|
||||
});
|
||||
}
|
||||
}
|
||||
84
lib/pangea/join_codes/knocked_rooms_model.dart
Normal file
84
lib/pangea/join_codes/knocked_rooms_model.dart
Normal file
|
|
@ -0,0 +1,84 @@
|
|||
import 'package:collection/collection.dart';
|
||||
|
||||
class KnockedRoomsModel {
|
||||
final List<String> knockedRoomIds;
|
||||
final List<String> acceptedInviteRoomIds;
|
||||
|
||||
const KnockedRoomsModel({
|
||||
this.knockedRoomIds = const [],
|
||||
this.acceptedInviteRoomIds = const [],
|
||||
});
|
||||
|
||||
static const String _roomIdsField = 'room_ids';
|
||||
static const String _acceptedInviteRoomIdsField = 'accepted_invite_room_ids';
|
||||
|
||||
bool hasEverKnocked(String roomId) {
|
||||
return knockedRoomIds.contains(roomId) ||
|
||||
acceptedInviteRoomIds.contains(roomId);
|
||||
}
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
return {
|
||||
_roomIdsField: knockedRoomIds,
|
||||
_acceptedInviteRoomIdsField: acceptedInviteRoomIds,
|
||||
};
|
||||
}
|
||||
|
||||
factory KnockedRoomsModel.fromJson(Map<String, dynamic> json) {
|
||||
final knockedIds = json[_roomIdsField];
|
||||
final acceptedInviteIds = json[_acceptedInviteRoomIdsField];
|
||||
return KnockedRoomsModel(
|
||||
knockedRoomIds: knockedIds is List
|
||||
? List<String>.from(knockedIds)
|
||||
: <String>[],
|
||||
acceptedInviteRoomIds: acceptedInviteIds is List
|
||||
? List<String>.from(acceptedInviteIds)
|
||||
: <String>[],
|
||||
);
|
||||
}
|
||||
|
||||
KnockedRoomsModel copyWithKnockedRoom(String roomId) {
|
||||
final newKnockedRoomIds = List<String>.from(knockedRoomIds);
|
||||
if (!newKnockedRoomIds.contains(roomId)) {
|
||||
newKnockedRoomIds.add(roomId);
|
||||
}
|
||||
|
||||
final newAcceptedInviteRoomIds = List<String>.from(acceptedInviteRoomIds)
|
||||
..remove(roomId);
|
||||
return KnockedRoomsModel(
|
||||
knockedRoomIds: newKnockedRoomIds,
|
||||
acceptedInviteRoomIds: newAcceptedInviteRoomIds,
|
||||
);
|
||||
}
|
||||
|
||||
KnockedRoomsModel copyWithAcceptedInviteRoom(String roomId) {
|
||||
final newAcceptedInviteRoomIds = List<String>.from(acceptedInviteRoomIds);
|
||||
if (!newAcceptedInviteRoomIds.contains(roomId)) {
|
||||
newAcceptedInviteRoomIds.add(roomId);
|
||||
}
|
||||
|
||||
final newKnockedRoomIds = List<String>.from(knockedRoomIds)..remove(roomId);
|
||||
return KnockedRoomsModel(
|
||||
knockedRoomIds: newKnockedRoomIds,
|
||||
acceptedInviteRoomIds: newAcceptedInviteRoomIds,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
if (identical(this, other)) return true;
|
||||
|
||||
return other is KnockedRoomsModel &&
|
||||
ListEquality().equals(other.knockedRoomIds, knockedRoomIds) &&
|
||||
ListEquality().equals(
|
||||
other.acceptedInviteRoomIds,
|
||||
acceptedInviteRoomIds,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
int get hashCode => Object.hash(
|
||||
ListEquality().hash(knockedRoomIds),
|
||||
ListEquality().hash(acceptedInviteRoomIds),
|
||||
);
|
||||
}
|
||||
|
|
@ -163,11 +163,21 @@ Future<void> notificationTap(
|
|||
}
|
||||
// Pangea#
|
||||
|
||||
router.go(
|
||||
client.getRoomById(roomId)?.membership == Membership.invite
|
||||
? '/rooms'
|
||||
: '/rooms/$roomId',
|
||||
);
|
||||
// #Pangea
|
||||
// router.go(
|
||||
// client.getRoomById(roomId)?.membership == Membership.invite
|
||||
// ? '/rooms'
|
||||
// : '/rooms/$roomId',
|
||||
// );
|
||||
final room = client.getRoomById(roomId);
|
||||
if (room?.membership == Membership.invite) {
|
||||
router.go('/rooms');
|
||||
} else if (room?.isSpace == true) {
|
||||
router.go('/rooms/spaces/$roomId');
|
||||
} else {
|
||||
router.go('/rooms/$roomId');
|
||||
}
|
||||
// Pangea#
|
||||
case NotificationResponseType.selectedNotificationAction:
|
||||
final actionType = FluffyChatNotificationActions.values.singleWhereOrNull(
|
||||
(action) => action.name == notificationResponse.actionId,
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@ import 'package:fluffychat/config/app_config.dart';
|
|||
import 'package:fluffychat/config/setting_keys.dart';
|
||||
import 'package:fluffychat/l10n/l10n.dart';
|
||||
import 'package:fluffychat/pangea/common/utils/error_handler.dart';
|
||||
import 'package:fluffychat/pangea/join_codes/knock_notification_utils.dart';
|
||||
import 'package:fluffychat/utils/client_download_content_extension.dart';
|
||||
import 'package:fluffychat/utils/client_manager.dart';
|
||||
import 'package:fluffychat/utils/matrix_sdk_extensions/matrix_locals.dart';
|
||||
|
|
@ -158,11 +159,25 @@ Future<void> _tryPushHelper(
|
|||
final matrixLocals = MatrixLocals(l10n);
|
||||
|
||||
// Calculate the body
|
||||
final body = event.type == EventTypes.Encrypted
|
||||
// #Pangea
|
||||
// ? l10n.newMessageInFluffyChat
|
||||
// #Pangea
|
||||
// final body = event.type == EventTypes.Encrypted
|
||||
// ? l10n.newMessageInFluffyChat
|
||||
// : await event.calcLocalizedBody(
|
||||
// matrixLocals,
|
||||
// plaintextBody: true,
|
||||
// withSenderNamePrefix: false,
|
||||
// hideReply: true,
|
||||
// hideEdit: true,
|
||||
// removeMarkdown: true,
|
||||
// );
|
||||
final hasKnocked = isKnockAcceptedInviteForClient(
|
||||
event: event,
|
||||
client: client,
|
||||
);
|
||||
final body = hasKnocked
|
||||
? l10n.knockAccepted
|
||||
: event.type == EventTypes.Encrypted
|
||||
? l10n.newMessageInPangeaChat
|
||||
// Pangea#
|
||||
: await event.calcLocalizedBody(
|
||||
matrixLocals,
|
||||
plaintextBody: true,
|
||||
|
|
@ -171,6 +186,7 @@ Future<void> _tryPushHelper(
|
|||
hideEdit: true,
|
||||
removeMarkdown: true,
|
||||
);
|
||||
// Pangea#
|
||||
|
||||
// The person object for the android message style notification
|
||||
final avatar = event.room.avatar;
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@ import 'package:universal_html/html.dart' as html;
|
|||
import 'package:fluffychat/config/setting_keys.dart';
|
||||
import 'package:fluffychat/l10n/l10n.dart';
|
||||
import 'package:fluffychat/pangea/common/utils/error_handler.dart';
|
||||
import 'package:fluffychat/pangea/join_codes/knock_notification_utils.dart';
|
||||
import 'package:fluffychat/utils/client_download_content_extension.dart';
|
||||
import 'package:fluffychat/utils/matrix_sdk_extensions/matrix_locals.dart';
|
||||
import 'package:fluffychat/utils/push_helper.dart';
|
||||
|
|
@ -35,16 +36,30 @@ extension LocalNotificationsExtension on MatrixState {
|
|||
final title = event.room.getLocalizedDisplayname(
|
||||
MatrixLocals(L10n.of(context)),
|
||||
);
|
||||
final body = await event.calcLocalizedBody(
|
||||
MatrixLocals(L10n.of(context)),
|
||||
withSenderNamePrefix:
|
||||
!event.room.isDirectChat ||
|
||||
event.room.lastEvent?.senderId == client.userID,
|
||||
plaintextBody: true,
|
||||
hideReply: true,
|
||||
hideEdit: true,
|
||||
removeMarkdown: true,
|
||||
);
|
||||
// #Pangea
|
||||
// final body = await event.calcLocalizedBody(
|
||||
// MatrixLocals(L10n.of(context)),
|
||||
// withSenderNamePrefix:
|
||||
// !event.room.isDirectChat ||
|
||||
// event.room.lastEvent?.senderId == client.userID,
|
||||
// plaintextBody: true,
|
||||
// hideReply: true,
|
||||
// hideEdit: true,
|
||||
// removeMarkdown: true,
|
||||
// );
|
||||
final body = isKnockAcceptedInviteForClient(event: event, client: client)
|
||||
? L10n.of(context).knockAccepted
|
||||
: await event.calcLocalizedBody(
|
||||
MatrixLocals(L10n.of(context)),
|
||||
withSenderNamePrefix:
|
||||
!event.room.isDirectChat ||
|
||||
event.room.lastEvent?.senderId == client.userID,
|
||||
plaintextBody: true,
|
||||
hideReply: true,
|
||||
hideEdit: true,
|
||||
removeMarkdown: true,
|
||||
);
|
||||
// Pangea#
|
||||
|
||||
if (kIsWeb) {
|
||||
// #Pangea
|
||||
|
|
|
|||
155
test/pangea/knock_notification_utils_test.dart
Normal file
155
test/pangea/knock_notification_utils_test.dart
Normal file
|
|
@ -0,0 +1,155 @@
|
|||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:matrix/matrix.dart';
|
||||
|
||||
import 'package:fluffychat/pangea/join_codes/knock_notification_utils.dart';
|
||||
import 'package:fluffychat/pangea/join_codes/knocked_rooms_model.dart';
|
||||
|
||||
void main() {
|
||||
const roomId = '!course:staging.pangea.chat';
|
||||
const userId = '@learner:staging.pangea.chat';
|
||||
const adminId = '@teacher:staging.pangea.chat';
|
||||
|
||||
group('isKnockAcceptedInvite', () {
|
||||
test('returns true when all conditions match a knock-accepted invite', () {
|
||||
final model = KnockedRoomsModel(
|
||||
knockedRoomIds: [roomId],
|
||||
acceptedInviteRoomIds: [],
|
||||
);
|
||||
final result = isKnockAcceptedInvite(
|
||||
eventType: EventTypes.RoomMember,
|
||||
newMembership: 'invite',
|
||||
stateKey: userId,
|
||||
currentUserId: userId,
|
||||
hasKnocked: model.hasEverKnocked(roomId),
|
||||
);
|
||||
expect(result, isTrue);
|
||||
});
|
||||
|
||||
test('returns false when event is not m.room.member', () {
|
||||
final model = KnockedRoomsModel(
|
||||
knockedRoomIds: [roomId],
|
||||
acceptedInviteRoomIds: [],
|
||||
);
|
||||
final result = isKnockAcceptedInvite(
|
||||
eventType: 'm.room.message',
|
||||
newMembership: 'invite',
|
||||
stateKey: userId,
|
||||
currentUserId: userId,
|
||||
hasKnocked: model.hasEverKnocked(roomId),
|
||||
);
|
||||
expect(result, isFalse);
|
||||
});
|
||||
|
||||
test('returns false when membership is not invite (e.g. join)', () {
|
||||
final model = KnockedRoomsModel(
|
||||
knockedRoomIds: [roomId],
|
||||
acceptedInviteRoomIds: [],
|
||||
);
|
||||
final result = isKnockAcceptedInvite(
|
||||
eventType: EventTypes.RoomMember,
|
||||
newMembership: 'join',
|
||||
stateKey: userId,
|
||||
currentUserId: userId,
|
||||
hasKnocked: model.hasEverKnocked(roomId),
|
||||
);
|
||||
expect(result, isFalse);
|
||||
});
|
||||
|
||||
test('returns false when membership is null', () {
|
||||
final model = KnockedRoomsModel(
|
||||
knockedRoomIds: [roomId],
|
||||
acceptedInviteRoomIds: [],
|
||||
);
|
||||
final result = isKnockAcceptedInvite(
|
||||
eventType: EventTypes.RoomMember,
|
||||
newMembership: null,
|
||||
stateKey: userId,
|
||||
currentUserId: userId,
|
||||
hasKnocked: model.hasEverKnocked(roomId),
|
||||
);
|
||||
expect(result, isFalse);
|
||||
});
|
||||
|
||||
test(
|
||||
'returns false when invite targets a different user (not current user)',
|
||||
() {
|
||||
final model = KnockedRoomsModel(
|
||||
knockedRoomIds: [roomId],
|
||||
acceptedInviteRoomIds: [],
|
||||
);
|
||||
final result = isKnockAcceptedInvite(
|
||||
eventType: EventTypes.RoomMember,
|
||||
newMembership: 'invite',
|
||||
stateKey: adminId, // <-- someone else was invited
|
||||
currentUserId: userId,
|
||||
hasKnocked: model.hasEverKnocked(roomId),
|
||||
);
|
||||
expect(result, isFalse);
|
||||
},
|
||||
);
|
||||
|
||||
test('returns false when stateKey is null', () {
|
||||
final model = KnockedRoomsModel(
|
||||
knockedRoomIds: [roomId],
|
||||
acceptedInviteRoomIds: [],
|
||||
);
|
||||
final result = isKnockAcceptedInvite(
|
||||
eventType: EventTypes.RoomMember,
|
||||
newMembership: 'invite',
|
||||
stateKey: null,
|
||||
currentUserId: userId,
|
||||
hasKnocked: model.hasEverKnocked(roomId),
|
||||
);
|
||||
expect(result, isFalse);
|
||||
});
|
||||
|
||||
test('returns false when the room was not previously knocked on', () {
|
||||
final model = KnockedRoomsModel(
|
||||
knockedRoomIds: [], // <-- no prior knock recorded
|
||||
acceptedInviteRoomIds: [],
|
||||
);
|
||||
final result = isKnockAcceptedInvite(
|
||||
eventType: EventTypes.RoomMember,
|
||||
newMembership: 'invite',
|
||||
stateKey: userId,
|
||||
currentUserId: userId,
|
||||
hasKnocked: model.hasEverKnocked(roomId),
|
||||
);
|
||||
expect(result, isFalse);
|
||||
});
|
||||
|
||||
test('returns false when a different room was knocked', () {
|
||||
final model = KnockedRoomsModel(
|
||||
knockedRoomIds: ['!other:staging.pangea.chat'],
|
||||
acceptedInviteRoomIds: [],
|
||||
);
|
||||
final result = isKnockAcceptedInvite(
|
||||
eventType: EventTypes.RoomMember,
|
||||
newMembership: 'invite',
|
||||
stateKey: userId,
|
||||
currentUserId: userId,
|
||||
hasKnocked: model.hasEverKnocked(roomId),
|
||||
);
|
||||
expect(result, isFalse);
|
||||
});
|
||||
|
||||
test('handles multiple knocked rooms and matches the correct one', () {
|
||||
final model = KnockedRoomsModel(
|
||||
knockedRoomIds: [
|
||||
'!other1:staging.pangea.chat',
|
||||
roomId,
|
||||
'!other2:staging.pangea.chat',
|
||||
],
|
||||
acceptedInviteRoomIds: [],
|
||||
);
|
||||
final result = isKnockAcceptedInvite(
|
||||
eventType: EventTypes.RoomMember,
|
||||
newMembership: 'invite',
|
||||
stateKey: userId,
|
||||
currentUserId: userId,
|
||||
hasKnocked: model.hasEverKnocked(roomId),
|
||||
);
|
||||
expect(result, isTrue);
|
||||
});
|
||||
});
|
||||
}
|
||||
Loading…
Add table
Reference in a new issue