Merge branch 'main' into 5262-add-seedsproutflower-icons-to-message-xp-burst

This commit is contained in:
Ava Shilling 2026-01-22 14:18:12 -05:00
commit b4f46938cb
122 changed files with 4938 additions and 1237 deletions

View file

@ -22,6 +22,7 @@ import 'package:fluffychat/pages/device_settings/device_settings.dart';
import 'package:fluffychat/pages/login/login.dart';
import 'package:fluffychat/pages/new_group/new_group.dart';
import 'package:fluffychat/pages/new_private_chat/new_private_chat.dart';
import 'package:fluffychat/pages/onboarding/space_code_onboarding.dart';
import 'package:fluffychat/pages/settings/settings.dart';
import 'package:fluffychat/pages/settings_3pid/settings_3pid.dart';
import 'package:fluffychat/pages/settings_chat/settings_chat.dart';
@ -50,9 +51,9 @@ import 'package:fluffychat/pangea/course_creation/course_invite_page.dart';
import 'package:fluffychat/pangea/course_creation/selected_course_page.dart';
import 'package:fluffychat/pangea/join_codes/join_with_link_page.dart';
import 'package:fluffychat/pangea/learning_settings/settings_learning.dart';
import 'package:fluffychat/pangea/login/pages/add_course_page.dart';
import 'package:fluffychat/pangea/login/pages/course_code_page.dart';
import 'package:fluffychat/pangea/login/pages/create_pangea_account_page.dart';
import 'package:fluffychat/pangea/login/pages/find_course_page.dart';
import 'package:fluffychat/pangea/login/pages/language_selection_page.dart';
import 'package:fluffychat/pangea/login/pages/login_or_signup_view.dart';
import 'package:fluffychat/pangea/login/pages/new_course_page.dart';
@ -213,93 +214,8 @@ abstract class AppRoutes {
pageBuilder: (context, state) => defaultPageBuilder(
context,
state,
const AddCoursePage(route: 'registration'),
const SpaceCodeOnboarding(),
),
routes: [
GoRoute(
path: 'private',
pageBuilder: (context, state) {
return defaultPageBuilder(
context,
state,
const CourseCodePage(),
);
},
),
GoRoute(
path: 'public',
pageBuilder: (context, state) {
return defaultPageBuilder(
context,
state,
const PublicCoursesPage(
route: 'registration',
showFilters: false,
),
);
},
routes: [
GoRoute(
path: ':courseid',
pageBuilder: (context, state) {
return defaultPageBuilder(
context,
state,
SelectedCourse(
state.pathParameters['courseid']!,
SelectedCourseMode.join,
roomChunk: state.extra as PublicRoomsChunk?,
),
);
},
),
],
),
GoRoute(
path: 'own',
pageBuilder: (context, state) {
return defaultPageBuilder(
context,
state,
const NewCoursePage(
route: 'registration',
showFilters: false,
),
);
},
routes: [
GoRoute(
path: ':courseid',
pageBuilder: (context, state) {
return defaultPageBuilder(
context,
state,
SelectedCourse(
state.pathParameters['courseid']!,
SelectedCourseMode.launch,
),
);
},
routes: [
GoRoute(
path: 'invite',
pageBuilder: (context, state) {
return defaultPageBuilder(
context,
state,
CourseInvitePage(
state.pathParameters['courseid']!,
courseCreationCompleter:
state.extra as Completer<String>?,
),
);
},
),
],
),
],
),
],
),
],
),
@ -434,7 +350,7 @@ abstract class AppRoutes {
pageBuilder: (context, state) => defaultPageBuilder(
context,
state,
const AddCoursePage(route: 'rooms'),
const FindCoursePage(),
),
routes: [
GoRoute(

View file

@ -1,6 +1,6 @@
{
"@@locale": "ar",
"@@last_modified": "2026-01-16 14:33:24.230348",
"@@last_modified": "2026-01-22 12:01:48.002470",
"about": "حول",
"@about": {
"type": "String",
@ -11082,5 +11082,64 @@
"@constructUseIncGCDesc": {
"type": "String",
"placeholders": {}
},
"constructUseCorGEDesc": "ممارسة تصحيح أخطاء القواعد",
"constructUseIncGEDesc": "ممارسة أخطاء القواعد غير الصحيحة",
"fillInBlank": "املأ الفراغ بالخيار الصحيح",
"learn": "تعلم",
"languageUpdated": "تم تحديث اللغة المستهدفة!",
"@constructUseCorGEDesc": {
"type": "String",
"placeholders": {}
},
"@constructUseIncGEDesc": {
"type": "String",
"placeholders": {}
},
"@fillInBlank": {
"type": "String",
"placeholders": {}
},
"@learn": {
"type": "String",
"placeholders": {}
},
"@languageUpdated": {
"type": "String",
"placeholders": {}
},
"voiceDropdownTitle": "صوت بوت بانجيا",
"@voiceDropdownTitle": {
"type": "String",
"placeholders": {}
},
"knockDesc": "تم إرسال طلبك إلى إدارة الدورة! سيتم السماح لك بالدخول إذا وافقوا.",
"@knockDesc": {
"type": "String",
"placeholders": {}
},
"joinSpaceOnboardingDesc": "هل لديك رمز دعوة أو رابط لدورة عامة؟",
"welcomeUser": "مرحبًا {user}",
"@joinSpaceOnboardingDesc": {
"type": "String",
"placeholders": {}
},
"@welcomeUser": {
"type": "String",
"placeholders": {
"user": {
"type": "String"
}
}
},
"publicInviteDescChat": "ابحث عن المستخدمين لدعوتهم إلى هذه الدردشة.",
"publicInviteDescSpace": "ابحث عن المستخدمين لدعوتهم إلى هذا الفضاء.",
"@publicInviteDescChat": {
"type": "String",
"placeholders": {}
},
"@publicInviteDescSpace": {
"type": "String",
"placeholders": {}
}
}

View file

@ -1911,7 +1911,7 @@
"playWithAI": "Пакуль гуляйце з ШІ",
"courseStartDesc": "Pangea Bot гатовы да працы ў любы час!\n\n...але навучанне лепш з сябрамі!",
"@@locale": "be",
"@@last_modified": "2026-01-16 14:33:05.869532",
"@@last_modified": "2026-01-22 12:01:33.641094",
"@alwaysUse24HourFormat": {
"type": "String",
"placeholders": {}
@ -11964,5 +11964,64 @@
"@constructUseIncGCDesc": {
"type": "String",
"placeholders": {}
},
"constructUseCorGEDesc": "Практыка правільнага выкарыстання граматычных памылак",
"constructUseIncGEDesc": "Практыка няправільнага выкарыстання граматычных памылак",
"fillInBlank": "Запоўніце прабел правільным выбарам",
"learn": "Навучыцца",
"languageUpdated": "Мэтавая мова абноўлена!",
"@constructUseCorGEDesc": {
"type": "String",
"placeholders": {}
},
"@constructUseIncGEDesc": {
"type": "String",
"placeholders": {}
},
"@fillInBlank": {
"type": "String",
"placeholders": {}
},
"@learn": {
"type": "String",
"placeholders": {}
},
"@languageUpdated": {
"type": "String",
"placeholders": {}
},
"voiceDropdownTitle": "Голас Pangea Bot",
"@voiceDropdownTitle": {
"type": "String",
"placeholders": {}
},
"knockDesc": "Ваш запыт быў адпраўлены адміністрацыі курса! Вы будзеце дапушчаны, калі яны зацвердзяць.",
"@knockDesc": {
"type": "String",
"placeholders": {}
},
"joinSpaceOnboardingDesc": "Ці маеце вы код запрашэння або спасылку на публічны курс?",
"welcomeUser": "Сардэчна запрашаем, {user}",
"@joinSpaceOnboardingDesc": {
"type": "String",
"placeholders": {}
},
"@welcomeUser": {
"type": "String",
"placeholders": {
"user": {
"type": "String"
}
}
},
"publicInviteDescChat": "Шукаць карыстальнікаў, каб запрасіць іх у гэты чат.",
"publicInviteDescSpace": "Шукаць карыстальнікаў, каб запрасіць іх у гэтае прастору.",
"@publicInviteDescChat": {
"type": "String",
"placeholders": {}
},
"@publicInviteDescSpace": {
"type": "String",
"placeholders": {}
}
}

View file

@ -1,5 +1,5 @@
{
"@@last_modified": "2026-01-16 14:33:49.084569",
"@@last_modified": "2026-01-22 12:02:02.719528",
"about": "সম্পর্কে",
"@about": {
"type": "String",
@ -11969,5 +11969,64 @@
"@constructUseIncGCDesc": {
"type": "String",
"placeholders": {}
},
"constructUseCorGEDesc": "সঠিক ব্যাকরণ ত্রুটি অনুশীলন",
"constructUseIncGEDesc": "ভুল ব্যাকরণ ত্রুটি অনুশীলন",
"fillInBlank": "সঠিক পছন্দ দিয়ে ফাঁকা স্থান পূরণ করুন",
"learn": "শিখুন",
"languageUpdated": "লক্ষ্য ভাষা আপডেট করা হয়েছে!",
"@constructUseCorGEDesc": {
"type": "String",
"placeholders": {}
},
"@constructUseIncGEDesc": {
"type": "String",
"placeholders": {}
},
"@fillInBlank": {
"type": "String",
"placeholders": {}
},
"@learn": {
"type": "String",
"placeholders": {}
},
"@languageUpdated": {
"type": "String",
"placeholders": {}
},
"voiceDropdownTitle": "প্যাঙ্গিয়া বটের কণ্ঠ",
"@voiceDropdownTitle": {
"type": "String",
"placeholders": {}
},
"knockDesc": "আপনার অনুরোধ কোর্স প্রশাসকের কাছে পাঠানো হয়েছে! তারা অনুমোদন করলে আপনাকে প্রবেশ করতে দেওয়া হবে।",
"@knockDesc": {
"type": "String",
"placeholders": {}
},
"joinSpaceOnboardingDesc": "আপনার কি একটি আমন্ত্রণ কোড বা একটি পাবলিক কোর্সের লিঙ্ক আছে?",
"welcomeUser": "স্বাগতম {user}",
"@joinSpaceOnboardingDesc": {
"type": "String",
"placeholders": {}
},
"@welcomeUser": {
"type": "String",
"placeholders": {
"user": {
"type": "String"
}
}
},
"publicInviteDescChat": "এই চ্যাটে আমন্ত্রণ জানানোর জন্য ব্যবহারকারীদের খুঁজুন।",
"publicInviteDescSpace": "এই স্পেসে আমন্ত্রণ জানানোর জন্য ব্যবহারকারীদের খুঁজুন।",
"@publicInviteDescChat": {
"type": "String",
"placeholders": {}
},
"@publicInviteDescSpace": {
"type": "String",
"placeholders": {}
}
}

View file

@ -4279,7 +4279,7 @@
"joinPublicTrip": "མི་ཚེས་ལ་ལོག་འབད།",
"startOwnTrip": "ངེད་རང་གི་ལོག་ལ་སྦྱོར་བཅོས།",
"@@locale": "bo",
"@@last_modified": "2026-01-16 14:33:43.824560",
"@@last_modified": "2026-01-22 12:01:59.937396",
"@alwaysUse24HourFormat": {
"type": "String",
"placeholders": {}
@ -10619,5 +10619,64 @@
"@constructUseIncGCDesc": {
"type": "String",
"placeholders": {}
},
"constructUseCorGEDesc": "Praktik kesalahan tata bahasa yang benar",
"constructUseIncGEDesc": "Praktik kesalahan tata bahasa yang salah",
"fillInBlank": "Isi kekosongan dengan pilihan yang benar",
"learn": "Belajar",
"languageUpdated": "Bahasa target diperbarui!",
"@constructUseCorGEDesc": {
"type": "String",
"placeholders": {}
},
"@constructUseIncGEDesc": {
"type": "String",
"placeholders": {}
},
"@fillInBlank": {
"type": "String",
"placeholders": {}
},
"@learn": {
"type": "String",
"placeholders": {}
},
"@languageUpdated": {
"type": "String",
"placeholders": {}
},
"voiceDropdownTitle": "Pangea Bot voz",
"@voiceDropdownTitle": {
"type": "String",
"placeholders": {}
},
"knockDesc": "Yor requst has been sent to course admin! Yu'll be let in if dey approve.",
"@knockDesc": {
"type": "String",
"placeholders": {}
},
"joinSpaceOnboardingDesc": "Do you have an invite code or link to a public course?",
"welcomeUser": "Welcome {user}",
"@joinSpaceOnboardingDesc": {
"type": "String",
"placeholders": {}
},
"@welcomeUser": {
"type": "String",
"placeholders": {
"user": {
"type": "String"
}
}
},
"publicInviteDescChat": "Searc for users to invite them to this chat.",
"publicInviteDescSpace": "Searc for users to invite them to this space.",
"@publicInviteDescChat": {
"type": "String",
"placeholders": {}
},
"@publicInviteDescSpace": {
"type": "String",
"placeholders": {}
}
}

View file

@ -1,5 +1,5 @@
{
"@@last_modified": "2026-01-16 14:33:08.940071",
"@@last_modified": "2026-01-22 12:01:35.625715",
"about": "Quant a",
"@about": {
"type": "String",
@ -10889,5 +10889,64 @@
"@constructUseIncGCDesc": {
"type": "String",
"placeholders": {}
},
"constructUseCorGEDesc": "Pràctica d'errors gramaticals correctes",
"constructUseIncGEDesc": "Pràctica d'errors gramaticals incorrectes",
"fillInBlank": "Omple el buit amb l'elecció correcta",
"learn": "Aprendre",
"languageUpdated": "Idioma objectiu actualitzat!",
"@constructUseCorGEDesc": {
"type": "String",
"placeholders": {}
},
"@constructUseIncGEDesc": {
"type": "String",
"placeholders": {}
},
"@fillInBlank": {
"type": "String",
"placeholders": {}
},
"@learn": {
"type": "String",
"placeholders": {}
},
"@languageUpdated": {
"type": "String",
"placeholders": {}
},
"voiceDropdownTitle": "Veu del bot Pangea",
"@voiceDropdownTitle": {
"type": "String",
"placeholders": {}
},
"knockDesc": "La teva sol·licitud s'ha enviat a l'administrador del curs! Et deixaran entrar si ho aproven.",
"@knockDesc": {
"type": "String",
"placeholders": {}
},
"joinSpaceOnboardingDesc": "Tens un codi d'invitació o un enllaç a un curs públic?",
"welcomeUser": "Benvingut {user}",
"@joinSpaceOnboardingDesc": {
"type": "String",
"placeholders": {}
},
"@welcomeUser": {
"type": "String",
"placeholders": {
"user": {
"type": "String"
}
}
},
"publicInviteDescChat": "Cerca usuaris per convidar-los a aquest xat.",
"publicInviteDescSpace": "Cerca usuaris per convidar-los a aquest espai.",
"@publicInviteDescChat": {
"type": "String",
"placeholders": {}
},
"@publicInviteDescSpace": {
"type": "String",
"placeholders": {}
}
}

View file

@ -1,6 +1,6 @@
{
"@@locale": "cs",
"@@last_modified": "2026-01-16 14:32:59.173160",
"@@last_modified": "2026-01-22 12:01:30.360444",
"about": "O aplikaci",
"@about": {
"type": "String",
@ -11472,5 +11472,64 @@
"@constructUseIncGCDesc": {
"type": "String",
"placeholders": {}
},
"constructUseCorGEDesc": "Cvičení správné gramatiky",
"constructUseIncGEDesc": "Cvičení nesprávné gramatiky",
"fillInBlank": "Doplňte prázdné místo správnou volbou",
"learn": "Učit se",
"languageUpdated": "Cílový jazyk byl aktualizován!",
"@constructUseCorGEDesc": {
"type": "String",
"placeholders": {}
},
"@constructUseIncGEDesc": {
"type": "String",
"placeholders": {}
},
"@fillInBlank": {
"type": "String",
"placeholders": {}
},
"@learn": {
"type": "String",
"placeholders": {}
},
"@languageUpdated": {
"type": "String",
"placeholders": {}
},
"voiceDropdownTitle": "Hlas Pangea Bota",
"@voiceDropdownTitle": {
"type": "String",
"placeholders": {}
},
"knockDesc": "Vaše žádost byla odeslána administrátorovi kurzu! Budete vpuštěni, pokud ji schválí.",
"@knockDesc": {
"type": "String",
"placeholders": {}
},
"joinSpaceOnboardingDesc": "Máte pozvánkový kód nebo odkaz na veřejný kurz?",
"welcomeUser": "Vítejte {user}",
"@joinSpaceOnboardingDesc": {
"type": "String",
"placeholders": {}
},
"@welcomeUser": {
"type": "String",
"placeholders": {
"user": {
"type": "String"
}
}
},
"publicInviteDescChat": "Hledejte uživatele, které chcete pozvat do tohoto chatu.",
"publicInviteDescSpace": "Hledejte uživatele, které chcete pozvat do tohoto prostoru.",
"@publicInviteDescChat": {
"type": "String",
"placeholders": {}
},
"@publicInviteDescSpace": {
"type": "String",
"placeholders": {}
}
}

View file

@ -1930,7 +1930,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-01-16 14:32:06.899995",
"@@last_modified": "2026-01-22 12:00:57.389253",
"@aboutHomeserver": {
"type": "String",
"placeholders": {
@ -11926,5 +11926,64 @@
"@constructUseIncGCDesc": {
"type": "String",
"placeholders": {}
},
"constructUseCorGEDesc": "Korrekt grammatikfejl praksis",
"constructUseIncGEDesc": "Ukorrrekt grammatikfejl praksis",
"fillInBlank": "Udfyld det tomme felt med det korrekte valg",
"learn": "Lær",
"languageUpdated": "Mål sprog opdateret!",
"@constructUseCorGEDesc": {
"type": "String",
"placeholders": {}
},
"@constructUseIncGEDesc": {
"type": "String",
"placeholders": {}
},
"@fillInBlank": {
"type": "String",
"placeholders": {}
},
"@learn": {
"type": "String",
"placeholders": {}
},
"@languageUpdated": {
"type": "String",
"placeholders": {}
},
"voiceDropdownTitle": "Pangea Bot stemme",
"@voiceDropdownTitle": {
"type": "String",
"placeholders": {}
},
"knockDesc": "Din anmodning er sendt til kursusadministratoren! Du vil blive lukket ind, hvis de godkender.",
"@knockDesc": {
"type": "String",
"placeholders": {}
},
"joinSpaceOnboardingDesc": "Har du en invitationskode eller et link til et offentligt kursus?",
"welcomeUser": "Velkommen {user}",
"@joinSpaceOnboardingDesc": {
"type": "String",
"placeholders": {}
},
"@welcomeUser": {
"type": "String",
"placeholders": {
"user": {
"type": "String"
}
}
},
"publicInviteDescChat": "Søg efter brugere for at invitere dem til denne chat.",
"publicInviteDescSpace": "Søg efter brugere for at invitere dem til dette rum.",
"@publicInviteDescChat": {
"type": "String",
"placeholders": {}
},
"@publicInviteDescSpace": {
"type": "String",
"placeholders": {}
}
}

View file

@ -1,6 +1,6 @@
{
"@@locale": "de",
"@@last_modified": "2026-01-16 14:32:44.617501",
"@@last_modified": "2026-01-22 12:01:22.683777",
"alwaysUse24HourFormat": "true",
"@alwaysUse24HourFormat": {
"description": "Set to true to always display time of day in 24 hour format."
@ -10872,5 +10872,64 @@
"@constructUseIncGCDesc": {
"type": "String",
"placeholders": {}
},
"constructUseCorGEDesc": "Korrekte Grammatikfehlerübung",
"constructUseIncGEDesc": "Falsche Grammatikfehlerübung",
"fillInBlank": "Füllen Sie die Lücke mit der richtigen Wahl aus",
"learn": "Lernen",
"languageUpdated": "Zielsprache aktualisiert!",
"@constructUseCorGEDesc": {
"type": "String",
"placeholders": {}
},
"@constructUseIncGEDesc": {
"type": "String",
"placeholders": {}
},
"@fillInBlank": {
"type": "String",
"placeholders": {}
},
"@learn": {
"type": "String",
"placeholders": {}
},
"@languageUpdated": {
"type": "String",
"placeholders": {}
},
"voiceDropdownTitle": "Pangea Bot Stimme",
"@voiceDropdownTitle": {
"type": "String",
"placeholders": {}
},
"knockDesc": "Ihre Anfrage wurde an den Kursadministrator gesendet! Sie werden eingelassen, wenn sie zustimmen.",
"@knockDesc": {
"type": "String",
"placeholders": {}
},
"joinSpaceOnboardingDesc": "Haben Sie einen Einladungscode oder einen Link zu einem öffentlichen Kurs?",
"welcomeUser": "Willkommen {user}",
"@joinSpaceOnboardingDesc": {
"type": "String",
"placeholders": {}
},
"@welcomeUser": {
"type": "String",
"placeholders": {
"user": {
"type": "String"
}
}
},
"publicInviteDescChat": "Suchen Sie nach Benutzern, um sie zu diesem Chat einzuladen.",
"publicInviteDescSpace": "Suchen Sie nach Benutzern, um sie zu diesem Raum einzuladen.",
"@publicInviteDescChat": {
"type": "String",
"placeholders": {}
},
"@publicInviteDescSpace": {
"type": "String",
"placeholders": {}
}
}

View file

@ -4456,7 +4456,7 @@
"playWithAI": "Παίξτε με την Τεχνητή Νοημοσύνη προς το παρόν",
"courseStartDesc": "Ο Pangea Bot είναι έτοιμος να ξεκινήσει οποιαδήποτε στιγμή!\n\n...αλλά η μάθηση είναι καλύτερη με φίλους!",
"@@locale": "el",
"@@last_modified": "2026-01-16 14:33:59.313114",
"@@last_modified": "2026-01-22 12:02:10.279313",
"@alwaysUse24HourFormat": {
"type": "String",
"placeholders": {}
@ -11923,5 +11923,64 @@
"@constructUseIncGCDesc": {
"type": "String",
"placeholders": {}
},
"constructUseCorGEDesc": "Πρακτική διόρθωσης γραμματικών λαθών",
"constructUseIncGEDesc": "Πρακτική λανθασμένων γραμματικών λαθών",
"fillInBlank": "Συμπληρώστε το κενό με τη σωστή επιλογή",
"learn": "Μάθετε",
"languageUpdated": "Η γλώσσα στόχος ενημερώθηκε!",
"@constructUseCorGEDesc": {
"type": "String",
"placeholders": {}
},
"@constructUseIncGEDesc": {
"type": "String",
"placeholders": {}
},
"@fillInBlank": {
"type": "String",
"placeholders": {}
},
"@learn": {
"type": "String",
"placeholders": {}
},
"@languageUpdated": {
"type": "String",
"placeholders": {}
},
"voiceDropdownTitle": "Φωνή Pangea Bot",
"@voiceDropdownTitle": {
"type": "String",
"placeholders": {}
},
"knockDesc": "Το αίτημά σας έχει σταλεί στον διαχειριστή του μαθήματος! Θα σας επιτρέψουν να μπείτε αν το εγκρίνουν.",
"@knockDesc": {
"type": "String",
"placeholders": {}
},
"joinSpaceOnboardingDesc": "Έχετε έναν κωδικό πρόσκλησης ή σύνδεσμο για ένα δημόσιο μάθημα;",
"welcomeUser": "Καλώς ήρθατε {user}",
"@joinSpaceOnboardingDesc": {
"type": "String",
"placeholders": {}
},
"@welcomeUser": {
"type": "String",
"placeholders": {
"user": {
"type": "String"
}
}
},
"publicInviteDescChat": "Αναζητήστε χρήστες για να τους προσκαλέσετε σε αυτήν την συνομιλία.",
"publicInviteDescSpace": "Αναζητήστε χρήστες για να τους προσκαλέσετε σε αυτόν τον χώρο.",
"@publicInviteDescChat": {
"type": "String",
"placeholders": {}
},
"@publicInviteDescSpace": {
"type": "String",
"placeholders": {}
}
}

View file

@ -5054,5 +5054,21 @@
"constructUseIncGCDesc": "Incorrect grammar category practice",
"constructUseCorGEDesc": "Correct grammar error practice",
"constructUseIncGEDesc": "Incorrect grammar error practice",
"fillInBlank": "Fill in the blank with the correct choice"
"fillInBlank": "Fill in the blank with the correct choice",
"learn": "Learn",
"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.",
"joinSpaceOnboardingDesc": "Do you have an invite code or link to a public course?",
"welcomeUser": "Welcome {user}",
"@welcomeUser": {
"placeholders": {
"user": {
"type": "String"
}
}
},
"findCourse": "Find a course",
"publicInviteDescChat": "Search for users to invite them to this chat.",
"publicInviteDescSpace": "Search for users to invite them to this space."
}

View file

@ -1,5 +1,5 @@
{
"@@last_modified": "2026-01-16 14:34:08.644512",
"@@last_modified": "2026-01-22 12:02:15.725740",
"about": "Prio",
"@about": {
"type": "String",
@ -11954,5 +11954,64 @@
"@constructUseIncGCDesc": {
"type": "String",
"placeholders": {}
},
"constructUseCorGEDesc": "Praktiko pri ĝusta gramatika eraro",
"constructUseIncGEDesc": "Praktiko pri malĝusta gramatika eraro",
"fillInBlank": "Plenigu la malplenan lokon per la ĝusta elekto",
"learn": "Lerni",
"languageUpdated": "Celo lingvo ĝisdatigita!",
"@constructUseCorGEDesc": {
"type": "String",
"placeholders": {}
},
"@constructUseIncGEDesc": {
"type": "String",
"placeholders": {}
},
"@fillInBlank": {
"type": "String",
"placeholders": {}
},
"@learn": {
"type": "String",
"placeholders": {}
},
"@languageUpdated": {
"type": "String",
"placeholders": {}
},
"voiceDropdownTitle": "Voĉo de Pangea Bot",
"@voiceDropdownTitle": {
"type": "String",
"placeholders": {}
},
"knockDesc": "Via peto estis sendita al la kursa administranto! Vi estos enirita se ili aprobas.",
"@knockDesc": {
"type": "String",
"placeholders": {}
},
"joinSpaceOnboardingDesc": "Ĉu vi havas invitkodon aŭ ligon al publika kurso?",
"welcomeUser": "Bonvenon {user}",
"@joinSpaceOnboardingDesc": {
"type": "String",
"placeholders": {}
},
"@welcomeUser": {
"type": "String",
"placeholders": {
"user": {
"type": "String"
}
}
},
"publicInviteDescChat": "Serĉu uzantojn por inviti ilin al ĉi tiu konversacio.",
"publicInviteDescSpace": "Serĉu uzantojn por inviti ilin al ĉi tiu spaco.",
"@publicInviteDescChat": {
"type": "String",
"placeholders": {}
},
"@publicInviteDescSpace": {
"type": "String",
"placeholders": {}
}
}

View file

@ -1,6 +1,6 @@
{
"@@locale": "es",
"@@last_modified": "2026-01-16 14:31:58.653004",
"@@last_modified": "2026-01-22 12:00:51.625942",
"about": "Acerca de",
"@about": {
"type": "String",
@ -8099,5 +8099,64 @@
"@constructUseIncGCDesc": {
"type": "String",
"placeholders": {}
},
"constructUseCorGEDesc": "Práctica de errores gramaticales correctos",
"constructUseIncGEDesc": "Práctica de errores gramaticales incorrectos",
"fillInBlank": "Completa el espacio en blanco con la opción correcta",
"learn": "Aprender",
"languageUpdated": "¡Idioma objetivo actualizado!",
"@constructUseCorGEDesc": {
"type": "String",
"placeholders": {}
},
"@constructUseIncGEDesc": {
"type": "String",
"placeholders": {}
},
"@fillInBlank": {
"type": "String",
"placeholders": {}
},
"@learn": {
"type": "String",
"placeholders": {}
},
"@languageUpdated": {
"type": "String",
"placeholders": {}
},
"voiceDropdownTitle": "Voz del bot Pangea",
"@voiceDropdownTitle": {
"type": "String",
"placeholders": {}
},
"knockDesc": "¡Tu solicitud ha sido enviada al administrador del curso! Te dejarán entrar si la aprueban.",
"@knockDesc": {
"type": "String",
"placeholders": {}
},
"joinSpaceOnboardingDesc": "¿Tienes un código de invitación o un enlace a un curso público?",
"welcomeUser": "Bienvenido {user}",
"@joinSpaceOnboardingDesc": {
"type": "String",
"placeholders": {}
},
"@welcomeUser": {
"type": "String",
"placeholders": {
"user": {
"type": "String"
}
}
},
"publicInviteDescChat": "Busca usuarios para invitarlos a este chat.",
"publicInviteDescSpace": "Busca usuarios para invitarlos a este espacio.",
"@publicInviteDescChat": {
"type": "String",
"placeholders": {}
},
"@publicInviteDescSpace": {
"type": "String",
"placeholders": {}
}
}

View file

@ -1,6 +1,6 @@
{
"@@locale": "et",
"@@last_modified": "2026-01-16 14:32:41.585481",
"@@last_modified": "2026-01-22 12:01:20.400166",
"about": "Rakenduse teave",
"@about": {
"type": "String",
@ -11136,5 +11136,64 @@
"@constructUseIncGCDesc": {
"type": "String",
"placeholders": {}
},
"constructUseCorGEDesc": "Õige grammatika vea harjutamine",
"constructUseIncGEDesc": "Vale grammatika vea harjutamine",
"fillInBlank": "Täida tühik õige valikuga",
"learn": "Õpi",
"languageUpdated": "Sihtkeel on uuendatud!",
"@constructUseCorGEDesc": {
"type": "String",
"placeholders": {}
},
"@constructUseIncGEDesc": {
"type": "String",
"placeholders": {}
},
"@fillInBlank": {
"type": "String",
"placeholders": {}
},
"@learn": {
"type": "String",
"placeholders": {}
},
"@languageUpdated": {
"type": "String",
"placeholders": {}
},
"voiceDropdownTitle": "Pangea Boti hääl",
"@voiceDropdownTitle": {
"type": "String",
"placeholders": {}
},
"knockDesc": "Teie taotlus on saadetud kursuse administraatorile! Teid lastakse sisse, kui nad heaks kiidavad.",
"@knockDesc": {
"type": "String",
"placeholders": {}
},
"joinSpaceOnboardingDesc": "Kas sul on kutsekood või link avalikule kursusele?",
"welcomeUser": "Tere tulemast {user}",
"@joinSpaceOnboardingDesc": {
"type": "String",
"placeholders": {}
},
"@welcomeUser": {
"type": "String",
"placeholders": {
"user": {
"type": "String"
}
}
},
"publicInviteDescChat": "Otsi kasutajaid, et neid sellesse vestlusse kutsuda.",
"publicInviteDescSpace": "Otsi kasutajaid, et neid sellesse ruumi kutsuda.",
"@publicInviteDescChat": {
"type": "String",
"placeholders": {}
},
"@publicInviteDescSpace": {
"type": "String",
"placeholders": {}
}
}

View file

@ -1,6 +1,6 @@
{
"@@locale": "eu",
"@@last_modified": "2026-01-16 14:32:36.050138",
"@@last_modified": "2026-01-22 12:01:17.457241",
"about": "Honi buruz",
"@about": {
"type": "String",
@ -10865,5 +10865,64 @@
"@constructUseIncGCDesc": {
"type": "String",
"placeholders": {}
},
"constructUseCorGEDesc": "Gramatika akats zuzenketa praktika",
"constructUseIncGEDesc": "Gramatika akats okerra praktika",
"fillInBlank": "Betekoa bete aukerarik egokienarekin",
"learn": "Ikasi",
"languageUpdated": "Helmuga hizkuntza eguneratua!",
"@constructUseCorGEDesc": {
"type": "String",
"placeholders": {}
},
"@constructUseIncGEDesc": {
"type": "String",
"placeholders": {}
},
"@fillInBlank": {
"type": "String",
"placeholders": {}
},
"@learn": {
"type": "String",
"placeholders": {}
},
"@languageUpdated": {
"type": "String",
"placeholders": {}
},
"voiceDropdownTitle": "Pangea Bot ahotsa",
"@voiceDropdownTitle": {
"type": "String",
"placeholders": {}
},
"knockDesc": "Zure eskaera ikastaroaren administratzaileari bidali zaio! Onartzen badute, sartuko zara.",
"@knockDesc": {
"type": "String",
"placeholders": {}
},
"joinSpaceOnboardingDesc": "Baduzu gonbidapen kodea edo lotura publiko baten ikastaroarentzako?",
"welcomeUser": "Ongi etorri {user}",
"@joinSpaceOnboardingDesc": {
"type": "String",
"placeholders": {}
},
"@welcomeUser": {
"type": "String",
"placeholders": {
"user": {
"type": "String"
}
}
},
"publicInviteDescChat": "Bilatu erabiltzaileak txat honetara gonbidatzeko.",
"publicInviteDescSpace": "Bilatu erabiltzaileak espazio honetara gonbidatzeko.",
"@publicInviteDescChat": {
"type": "String",
"placeholders": {}
},
"@publicInviteDescSpace": {
"type": "String",
"placeholders": {}
}
}

View file

@ -1,5 +1,5 @@
{
"@@last_modified": "2026-01-16 14:33:51.512389",
"@@last_modified": "2026-01-22 12:02:04.083596",
"repeatPassword": "تکرار رمزعبور",
"@repeatPassword": {},
"about": "درباره",
@ -11597,5 +11597,64 @@
"@constructUseIncGCDesc": {
"type": "String",
"placeholders": {}
},
"constructUseCorGEDesc": "تمرین خطای گرامری صحیح",
"constructUseIncGEDesc": "تمرین خطای گرامری نادرست",
"fillInBlank": "جای خالی را با گزینه صحیح پر کنید",
"learn": "یاد بگیرید",
"languageUpdated": "زبان هدف به‌روزرسانی شد!",
"@constructUseCorGEDesc": {
"type": "String",
"placeholders": {}
},
"@constructUseIncGEDesc": {
"type": "String",
"placeholders": {}
},
"@fillInBlank": {
"type": "String",
"placeholders": {}
},
"@learn": {
"type": "String",
"placeholders": {}
},
"@languageUpdated": {
"type": "String",
"placeholders": {}
},
"voiceDropdownTitle": "صدای ربات پانژیا",
"@voiceDropdownTitle": {
"type": "String",
"placeholders": {}
},
"knockDesc": "درخواست شما به مدیر دوره ارسال شده است! اگر آنها تأیید کنند، شما وارد خواهید شد.",
"@knockDesc": {
"type": "String",
"placeholders": {}
},
"joinSpaceOnboardingDesc": "آیا کد دعوت یا لینکی به یک دوره عمومی دارید؟",
"welcomeUser": "خوش آمدید {user}",
"@joinSpaceOnboardingDesc": {
"type": "String",
"placeholders": {}
},
"@welcomeUser": {
"type": "String",
"placeholders": {
"user": {
"type": "String"
}
}
},
"publicInviteDescChat": "برای دعوت کاربران به این چت، جستجو کنید.",
"publicInviteDescSpace": "برای دعوت کاربران به این فضا، جستجو کنید.",
"@publicInviteDescChat": {
"type": "String",
"placeholders": {}
},
"@publicInviteDescSpace": {
"type": "String",
"placeholders": {}
}
}

View file

@ -4009,7 +4009,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-01-16 14:32:03.875087",
"@@last_modified": "2026-01-22 12:00:55.098205",
"@alwaysUse24HourFormat": {
"type": "String",
"placeholders": {}
@ -11488,5 +11488,64 @@
"@constructUseIncGCDesc": {
"type": "String",
"placeholders": {}
},
"constructUseCorGEDesc": "Oikean kielioppivirheen harjoittelu",
"constructUseIncGEDesc": "Väärän kielioppivirheen harjoittelu",
"fillInBlank": "Täytä tyhjä kohta oikealla valinnalla",
"learn": "Oppia",
"languageUpdated": "Kohdekieli päivitetty!",
"@constructUseCorGEDesc": {
"type": "String",
"placeholders": {}
},
"@constructUseIncGEDesc": {
"type": "String",
"placeholders": {}
},
"@fillInBlank": {
"type": "String",
"placeholders": {}
},
"@learn": {
"type": "String",
"placeholders": {}
},
"@languageUpdated": {
"type": "String",
"placeholders": {}
},
"voiceDropdownTitle": "Pangea Botin ääni",
"@voiceDropdownTitle": {
"type": "String",
"placeholders": {}
},
"knockDesc": "Pyyntösi on lähetetty kurssin ylläpitäjälle! Sinut päästetään sisään, jos he hyväksyvät sen.",
"@knockDesc": {
"type": "String",
"placeholders": {}
},
"joinSpaceOnboardingDesc": "Onko sinulla kutsukoodia tai linkkiä julkiseen kurssiin?",
"welcomeUser": "Tervetuloa {user}",
"@joinSpaceOnboardingDesc": {
"type": "String",
"placeholders": {}
},
"@welcomeUser": {
"type": "String",
"placeholders": {
"user": {
"type": "String"
}
}
},
"publicInviteDescChat": "Etsi käyttäjiä kutsuaksesi heidät tähän keskusteluun.",
"publicInviteDescSpace": "Etsi käyttäjiä kutsuaksesi heidät tähän tilaan.",
"@publicInviteDescChat": {
"type": "String",
"placeholders": {}
},
"@publicInviteDescSpace": {
"type": "String",
"placeholders": {}
}
}

View file

@ -2787,7 +2787,7 @@
"selectAll": "Piliin lahat",
"deselectAll": "Huwag piliin lahat",
"@@locale": "fil",
"@@last_modified": "2026-01-16 14:33:18.880694",
"@@last_modified": "2026-01-22 12:01:44.028462",
"@setCustomPermissionLevel": {
"type": "String",
"placeholders": {}
@ -11841,5 +11841,64 @@
"@constructUseIncGCDesc": {
"type": "String",
"placeholders": {}
},
"constructUseCorGEDesc": "Pagsasanay sa tamang pagkakamali sa gramatika",
"constructUseIncGEDesc": "Pagsasanay sa maling pagkakamali sa gramatika",
"fillInBlank": "Punan ang blangko ng tamang pagpipilian",
"learn": "Matuto",
"languageUpdated": "Na-update ang target na wika!",
"@constructUseCorGEDesc": {
"type": "String",
"placeholders": {}
},
"@constructUseIncGEDesc": {
"type": "String",
"placeholders": {}
},
"@fillInBlank": {
"type": "String",
"placeholders": {}
},
"@learn": {
"type": "String",
"placeholders": {}
},
"@languageUpdated": {
"type": "String",
"placeholders": {}
},
"voiceDropdownTitle": "Boses ng Pangea Bot",
"@voiceDropdownTitle": {
"type": "String",
"placeholders": {}
},
"knockDesc": "Ang iyong kahilingan ay naipadala sa admin ng kurso! Papayagan ka nilang pumasok kung sila ay mag-aapruba.",
"@knockDesc": {
"type": "String",
"placeholders": {}
},
"joinSpaceOnboardingDesc": "Mayroon ka bang invite code o link sa isang pampublikong kurso?",
"welcomeUser": "Maligayang pagdating {user}",
"@joinSpaceOnboardingDesc": {
"type": "String",
"placeholders": {}
},
"@welcomeUser": {
"type": "String",
"placeholders": {
"user": {
"type": "String"
}
}
},
"publicInviteDescChat": "Maghanap ng mga gumagamit upang imbitahan sila sa chat na ito.",
"publicInviteDescSpace": "Maghanap ng mga gumagamit upang imbitahan sila sa espasyong ito.",
"@publicInviteDescChat": {
"type": "String",
"placeholders": {}
},
"@publicInviteDescSpace": {
"type": "String",
"placeholders": {}
}
}

View file

@ -1,6 +1,6 @@
{
"@@locale": "fr",
"@@last_modified": "2026-01-16 14:34:20.722657",
"@@last_modified": "2026-01-22 12:02:23.383738",
"about": "À propos",
"@about": {
"type": "String",
@ -11189,5 +11189,64 @@
"@constructUseIncGCDesc": {
"type": "String",
"placeholders": {}
},
"constructUseCorGEDesc": "Pratique de correction des erreurs grammaticales",
"constructUseIncGEDesc": "Pratique des erreurs grammaticales incorrectes",
"fillInBlank": "Remplissez le blanc avec le choix correct",
"learn": "Apprendre",
"languageUpdated": "Langue cible mise à jour !",
"@constructUseCorGEDesc": {
"type": "String",
"placeholders": {}
},
"@constructUseIncGEDesc": {
"type": "String",
"placeholders": {}
},
"@fillInBlank": {
"type": "String",
"placeholders": {}
},
"@learn": {
"type": "String",
"placeholders": {}
},
"@languageUpdated": {
"type": "String",
"placeholders": {}
},
"voiceDropdownTitle": "Voix du bot Pangea",
"@voiceDropdownTitle": {
"type": "String",
"placeholders": {}
},
"knockDesc": "Votre demande a été envoyée à l'administrateur du cours ! Vous serez admis s'ils approuvent.",
"@knockDesc": {
"type": "String",
"placeholders": {}
},
"joinSpaceOnboardingDesc": "Avez-vous un code d'invitation ou un lien vers un cours public ?",
"welcomeUser": "Bienvenue {user}",
"@joinSpaceOnboardingDesc": {
"type": "String",
"placeholders": {}
},
"@welcomeUser": {
"type": "String",
"placeholders": {
"user": {
"type": "String"
}
}
},
"publicInviteDescChat": "Recherchez des utilisateurs pour les inviter à ce chat.",
"publicInviteDescSpace": "Recherchez des utilisateurs pour les inviter à cet espace.",
"@publicInviteDescChat": {
"type": "String",
"placeholders": {}
},
"@publicInviteDescSpace": {
"type": "String",
"placeholders": {}
}
}

View file

@ -4517,7 +4517,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-01-16 14:34:18.473304",
"@@last_modified": "2026-01-22 12:02:22.009738",
"@customReaction": {
"type": "String",
"placeholders": {}
@ -10863,5 +10863,64 @@
"@constructUseIncGCDesc": {
"type": "String",
"placeholders": {}
},
"constructUseCorGEDesc": "Cleachtadh ar earráidí gramadaí ceart",
"constructUseIncGEDesc": "Cleachtadh ar earráidí gramadaí míchruinn",
"fillInBlank": "Líon isteach an folt le rogha cheart",
"learn": "Foghlaim",
"languageUpdated": "Teanga sprioc nuashonraithe!",
"@constructUseCorGEDesc": {
"type": "String",
"placeholders": {}
},
"@constructUseIncGEDesc": {
"type": "String",
"placeholders": {}
},
"@fillInBlank": {
"type": "String",
"placeholders": {}
},
"@learn": {
"type": "String",
"placeholders": {}
},
"@languageUpdated": {
"type": "String",
"placeholders": {}
},
"voiceDropdownTitle": "guth Pangea Bot",
"@voiceDropdownTitle": {
"type": "String",
"placeholders": {}
},
"knockDesc": "Tá do hiarratas curtha chuig an riarachán cúrsa! Cuirfear isteach thú má cheadaíonn siad é.",
"@knockDesc": {
"type": "String",
"placeholders": {}
},
"joinSpaceOnboardingDesc": "An bhfuil cód cuireadh nó nasc agat do chúrsa poiblí?",
"welcomeUser": "Fáilte {user}",
"@joinSpaceOnboardingDesc": {
"type": "String",
"placeholders": {}
},
"@welcomeUser": {
"type": "String",
"placeholders": {
"user": {
"type": "String"
}
}
},
"publicInviteDescChat": "Cuardaigh úsáideoirí le cuireadh a thabhairt dóibh chuig an gcomhrá seo.",
"publicInviteDescSpace": "Cuardaigh úsáideoirí le cuireadh a thabhairt dóibh chuig an spás seo.",
"@publicInviteDescChat": {
"type": "String",
"placeholders": {}
},
"@publicInviteDescSpace": {
"type": "String",
"placeholders": {}
}
}

View file

@ -1,6 +1,6 @@
{
"@@locale": "gl",
"@@last_modified": "2026-01-16 14:32:01.213945",
"@@last_modified": "2026-01-22 12:00:52.883998",
"about": "Acerca de",
"@about": {
"type": "String",
@ -10862,5 +10862,64 @@
"@constructUseIncGCDesc": {
"type": "String",
"placeholders": {}
},
"constructUseCorGEDesc": "Práctica de erro gramatical correcto",
"constructUseIncGEDesc": "Práctica de erro gramatical incorrecto",
"fillInBlank": "Completa o espazo en branco coa opción correcta",
"learn": "Aprender",
"languageUpdated": "Idioma de destino actualizado!",
"@constructUseCorGEDesc": {
"type": "String",
"placeholders": {}
},
"@constructUseIncGEDesc": {
"type": "String",
"placeholders": {}
},
"@fillInBlank": {
"type": "String",
"placeholders": {}
},
"@learn": {
"type": "String",
"placeholders": {}
},
"@languageUpdated": {
"type": "String",
"placeholders": {}
},
"voiceDropdownTitle": "Voz do bot Pangea",
"@voiceDropdownTitle": {
"type": "String",
"placeholders": {}
},
"knockDesc": "A túa solicitude foi enviada ao administrador do curso! Serás admitido se a aproban.",
"@knockDesc": {
"type": "String",
"placeholders": {}
},
"joinSpaceOnboardingDesc": "Tes un código de invitación ou un enlace a un curso público?",
"welcomeUser": "Benvido {user}",
"@joinSpaceOnboardingDesc": {
"type": "String",
"placeholders": {}
},
"@welcomeUser": {
"type": "String",
"placeholders": {
"user": {
"type": "String"
}
}
},
"publicInviteDescChat": "Busca usuarios para convidalos a este chat.",
"publicInviteDescSpace": "Busca usuarios para convidalos a este espazo.",
"@publicInviteDescChat": {
"type": "String",
"placeholders": {}
},
"@publicInviteDescSpace": {
"type": "String",
"placeholders": {}
}
}

View file

@ -1,5 +1,5 @@
{
"@@last_modified": "2026-01-16 14:32:26.413376",
"@@last_modified": "2026-01-22 12:01:11.911490",
"about": "אודות",
"@about": {
"type": "String",
@ -11914,5 +11914,64 @@
"@constructUseIncGCDesc": {
"type": "String",
"placeholders": {}
},
"constructUseCorGEDesc": "תרגול תיקון שגיאות דקדוק",
"constructUseIncGEDesc": "תרגול שגיאות דקדוק לא נכונות",
"fillInBlank": "מלא את החסר עם הבחירה הנכונה",
"learn": "ללמוד",
"languageUpdated": "שפת היעד עודכנה!",
"@constructUseCorGEDesc": {
"type": "String",
"placeholders": {}
},
"@constructUseIncGEDesc": {
"type": "String",
"placeholders": {}
},
"@fillInBlank": {
"type": "String",
"placeholders": {}
},
"@learn": {
"type": "String",
"placeholders": {}
},
"@languageUpdated": {
"type": "String",
"placeholders": {}
},
"voiceDropdownTitle": "קול של פנגיאה בוט",
"@voiceDropdownTitle": {
"type": "String",
"placeholders": {}
},
"knockDesc": "הבקשה שלך נשלחה למנהל הקורס! תורשה להיכנס אם הם יאשרו.",
"@knockDesc": {
"type": "String",
"placeholders": {}
},
"joinSpaceOnboardingDesc": "האם יש לך קוד הזמנה או קישור לקורס ציבורי?",
"welcomeUser": "ברוך הבא {user}",
"@joinSpaceOnboardingDesc": {
"type": "String",
"placeholders": {}
},
"@welcomeUser": {
"type": "String",
"placeholders": {
"user": {
"type": "String"
}
}
},
"publicInviteDescChat": "חפש משתמשים כדי להזמין אותם לצ'אט הזה.",
"publicInviteDescSpace": "חפש משתמשים כדי להזמין אותם למקום הזה.",
"@publicInviteDescChat": {
"type": "String",
"placeholders": {}
},
"@publicInviteDescSpace": {
"type": "String",
"placeholders": {}
}
}

View file

@ -4483,7 +4483,7 @@
"playWithAI": "अभी के लिए एआई के साथ खेलें",
"courseStartDesc": "पैंजिया बॉट कभी भी जाने के लिए तैयार है!\n\n...लेकिन दोस्तों के साथ सीखना बेहतर है!",
"@@locale": "hi",
"@@last_modified": "2026-01-16 14:34:05.778231",
"@@last_modified": "2026-01-22 12:02:13.864252",
"@alwaysUse24HourFormat": {
"type": "String",
"placeholders": {}
@ -11950,5 +11950,64 @@
"@constructUseIncGCDesc": {
"type": "String",
"placeholders": {}
},
"constructUseCorGEDesc": "व्याकरण त्रुटि सुधार अभ्यास",
"constructUseIncGEDesc": "व्याकरण त्रुटि गलत अभ्यास",
"fillInBlank": "सही विकल्प के साथ रिक्त स्थान भरें",
"learn": "सीखें",
"languageUpdated": "लक्षित भाषा अपडेट की गई!",
"@constructUseCorGEDesc": {
"type": "String",
"placeholders": {}
},
"@constructUseIncGEDesc": {
"type": "String",
"placeholders": {}
},
"@fillInBlank": {
"type": "String",
"placeholders": {}
},
"@learn": {
"type": "String",
"placeholders": {}
},
"@languageUpdated": {
"type": "String",
"placeholders": {}
},
"voiceDropdownTitle": "पैंगिया बॉट की आवाज़",
"@voiceDropdownTitle": {
"type": "String",
"placeholders": {}
},
"knockDesc": "आपका अनुरोध पाठ्यक्रम प्रशासन को भेज दिया गया है! यदि वे स्वीकृत करते हैं, तो आपको अंदर जाने दिया जाएगा।",
"@knockDesc": {
"type": "String",
"placeholders": {}
},
"joinSpaceOnboardingDesc": "क्या आपके पास एक आमंत्रण कोड या सार्वजनिक पाठ्यक्रम के लिए लिंक है?",
"welcomeUser": "स्वागत है {user}",
"@joinSpaceOnboardingDesc": {
"type": "String",
"placeholders": {}
},
"@welcomeUser": {
"type": "String",
"placeholders": {
"user": {
"type": "String"
}
}
},
"publicInviteDescChat": "इस चैट में आमंत्रित करने के लिए उपयोगकर्ताओं की खोज करें।",
"publicInviteDescSpace": "इस स्थान में आमंत्रित करने के लिए उपयोगकर्ताओं की खोज करें।",
"@publicInviteDescChat": {
"type": "String",
"placeholders": {}
},
"@publicInviteDescSpace": {
"type": "String",
"placeholders": {}
}
}

View file

@ -1,6 +1,6 @@
{
"@@locale": "hr",
"@@last_modified": "2026-01-16 14:32:23.849622",
"@@last_modified": "2026-01-22 12:01:10.402528",
"about": "Informacije",
"@about": {
"type": "String",
@ -11237,5 +11237,64 @@
"@constructUseIncGCDesc": {
"type": "String",
"placeholders": {}
},
"constructUseCorGEDesc": "Prakticiranje ispravne gramatičke greške",
"constructUseIncGEDesc": "Prakticiranje pogrešne gramatičke greške",
"fillInBlank": "Ispunite prazno mjesto s ispravnim izborom",
"learn": "Učite",
"languageUpdated": "Ciljani jezik ažuriran!",
"@constructUseCorGEDesc": {
"type": "String",
"placeholders": {}
},
"@constructUseIncGEDesc": {
"type": "String",
"placeholders": {}
},
"@fillInBlank": {
"type": "String",
"placeholders": {}
},
"@learn": {
"type": "String",
"placeholders": {}
},
"@languageUpdated": {
"type": "String",
"placeholders": {}
},
"voiceDropdownTitle": "Pangea Bot glas",
"@voiceDropdownTitle": {
"type": "String",
"placeholders": {}
},
"knockDesc": "Vaš zahtjev je poslan administratoru tečaja! Bit ćete primljeni ako odobre.",
"@knockDesc": {
"type": "String",
"placeholders": {}
},
"joinSpaceOnboardingDesc": "Imate li pozivni kod ili link za javni tečaj?",
"welcomeUser": "Dobrodošli {user}",
"@joinSpaceOnboardingDesc": {
"type": "String",
"placeholders": {}
},
"@welcomeUser": {
"type": "String",
"placeholders": {
"user": {
"type": "String"
}
}
},
"publicInviteDescChat": "Pretražite korisnike kako biste ih pozvali u ovaj chat.",
"publicInviteDescSpace": "Pretražite korisnike kako biste ih pozvali u ovaj prostor.",
"@publicInviteDescChat": {
"type": "String",
"placeholders": {}
},
"@publicInviteDescSpace": {
"type": "String",
"placeholders": {}
}
}

View file

@ -1,6 +1,6 @@
{
"@@locale": "hu",
"@@last_modified": "2026-01-16 14:32:10.796625",
"@@last_modified": "2026-01-22 12:01:00.971468",
"about": "Névjegy",
"@about": {
"type": "String",
@ -10866,5 +10866,64 @@
"@constructUseIncGCDesc": {
"type": "String",
"placeholders": {}
},
"constructUseCorGEDesc": "Helyes nyelvtani hiba gyakorlás",
"constructUseIncGEDesc": "Helytelen nyelvtani hiba gyakorlás",
"fillInBlank": "Töltsd ki a hiányzó részt a helyes választással",
"learn": "Tanulj",
"languageUpdated": "Cél nyelv frissítve!",
"@constructUseCorGEDesc": {
"type": "String",
"placeholders": {}
},
"@constructUseIncGEDesc": {
"type": "String",
"placeholders": {}
},
"@fillInBlank": {
"type": "String",
"placeholders": {}
},
"@learn": {
"type": "String",
"placeholders": {}
},
"@languageUpdated": {
"type": "String",
"placeholders": {}
},
"voiceDropdownTitle": "Pangea Bot hang",
"@voiceDropdownTitle": {
"type": "String",
"placeholders": {}
},
"knockDesc": "A kérésed el lett küldve a kurzus adminisztrátorának! Be fogsz engedni, ha jóváhagyják.",
"@knockDesc": {
"type": "String",
"placeholders": {}
},
"joinSpaceOnboardingDesc": "Van meghívó kódod vagy linked egy nyilvános kurzushoz?",
"welcomeUser": "Üdvözöljük {user}",
"@joinSpaceOnboardingDesc": {
"type": "String",
"placeholders": {}
},
"@welcomeUser": {
"type": "String",
"placeholders": {
"user": {
"type": "String"
}
}
},
"publicInviteDescChat": "Keresd meg a felhasználókat, hogy meghívd őket erre a csevegésre.",
"publicInviteDescSpace": "Keresd meg a felhasználókat, hogy meghívd őket erre a térre.",
"@publicInviteDescChat": {
"type": "String",
"placeholders": {}
},
"@publicInviteDescSpace": {
"type": "String",
"placeholders": {}
}
}

View file

@ -1958,7 +1958,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-01-16 14:32:31.137719",
"@@last_modified": "2026-01-22 12:01:13.730279",
"@alwaysUse24HourFormat": {
"type": "String",
"placeholders": {}
@ -11943,5 +11943,64 @@
"@constructUseIncGCDesc": {
"type": "String",
"placeholders": {}
},
"constructUseCorGEDesc": "Praktiko de ĝusta gramatika eraro",
"constructUseIncGEDesc": "Praktiko de malĝusta gramatika eraro",
"fillInBlank": "Plenigu la malplenan lokon kun la ĝusta elekto",
"learn": "Lerni",
"languageUpdated": "Celo lingvo ĝisdatigita!",
"@constructUseCorGEDesc": {
"type": "String",
"placeholders": {}
},
"@constructUseIncGEDesc": {
"type": "String",
"placeholders": {}
},
"@fillInBlank": {
"type": "String",
"placeholders": {}
},
"@learn": {
"type": "String",
"placeholders": {}
},
"@languageUpdated": {
"type": "String",
"placeholders": {}
},
"voiceDropdownTitle": "Voix du bot Pangea",
"@voiceDropdownTitle": {
"type": "String",
"placeholders": {}
},
"knockDesc": "Tua peticio est missa ad administratorem cursuum! Te admittent si illi approbant.",
"@knockDesc": {
"type": "String",
"placeholders": {}
},
"joinSpaceOnboardingDesc": "¿Tienes un código de invitación o un enlace a un curso público?",
"welcomeUser": "Bienvenido {user}",
"@joinSpaceOnboardingDesc": {
"type": "String",
"placeholders": {}
},
"@welcomeUser": {
"type": "String",
"placeholders": {
"user": {
"type": "String"
}
}
},
"publicInviteDescChat": "Cerca per utenti per invitarli a questa chat.",
"publicInviteDescSpace": "Cerca per utenti per invitarli a questo spazio.",
"@publicInviteDescChat": {
"type": "String",
"placeholders": {}
},
"@publicInviteDescSpace": {
"type": "String",
"placeholders": {}
}
}

View file

@ -1,5 +1,5 @@
{
"@@last_modified": "2026-01-16 14:32:13.610901",
"@@last_modified": "2026-01-22 12:01:02.729155",
"setAsCanonicalAlias": "Atur sebagai alias utama",
"@setAsCanonicalAlias": {
"type": "String",
@ -10856,5 +10856,64 @@
"@constructUseIncGCDesc": {
"type": "String",
"placeholders": {}
},
"constructUseCorGEDesc": "Latihan kesalahan tata bahasa yang benar",
"constructUseIncGEDesc": "Latihan kesalahan tata bahasa yang salah",
"fillInBlank": "Isi kekosongan dengan pilihan yang benar",
"learn": "Belajar",
"languageUpdated": "Bahasa target diperbarui!",
"@constructUseCorGEDesc": {
"type": "String",
"placeholders": {}
},
"@constructUseIncGEDesc": {
"type": "String",
"placeholders": {}
},
"@fillInBlank": {
"type": "String",
"placeholders": {}
},
"@learn": {
"type": "String",
"placeholders": {}
},
"@languageUpdated": {
"type": "String",
"placeholders": {}
},
"voiceDropdownTitle": "Suara Pangea Bot",
"@voiceDropdownTitle": {
"type": "String",
"placeholders": {}
},
"knockDesc": "Permintaan Anda telah dikirim ke admin kursus! Anda akan diizinkan masuk jika mereka menyetujuinya.",
"@knockDesc": {
"type": "String",
"placeholders": {}
},
"joinSpaceOnboardingDesc": "Apakah Anda memiliki kode undangan atau tautan ke kursus publik?",
"welcomeUser": "Selamat datang {user}",
"@joinSpaceOnboardingDesc": {
"type": "String",
"placeholders": {}
},
"@welcomeUser": {
"type": "String",
"placeholders": {
"user": {
"type": "String"
}
}
},
"publicInviteDescChat": "Cari pengguna untuk mengundang mereka ke obrolan ini.",
"publicInviteDescSpace": "Cari pengguna untuk mengundang mereka ke ruang ini.",
"@publicInviteDescChat": {
"type": "String",
"placeholders": {}
},
"@publicInviteDescSpace": {
"type": "String",
"placeholders": {}
}
}

View file

@ -4372,7 +4372,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-01-16 14:32:21.289462",
"@@last_modified": "2026-01-22 12:01:08.955250",
"@alwaysUse24HourFormat": {
"type": "String",
"placeholders": {}
@ -11839,5 +11839,64 @@
"@constructUseIncGCDesc": {
"type": "String",
"placeholders": {}
},
"constructUseCorGEDesc": "Praktika korrekta gramatikfehler",
"constructUseIncGEDesc": "Praktika inkorrekt gramatikfehler",
"fillInBlank": "Fyll i tomrummet med det korrekta valget",
"learn": "Lær",
"languageUpdated": "Mål sprog opdateret!",
"@constructUseCorGEDesc": {
"type": "String",
"placeholders": {}
},
"@constructUseIncGEDesc": {
"type": "String",
"placeholders": {}
},
"@fillInBlank": {
"type": "String",
"placeholders": {}
},
"@learn": {
"type": "String",
"placeholders": {}
},
"@languageUpdated": {
"type": "String",
"placeholders": {}
},
"voiceDropdownTitle": "Pangea Bot guth",
"@voiceDropdownTitle": {
"type": "String",
"placeholders": {}
},
"knockDesc": "Tua iarrtas a chaidh a chur gu rianachd a' chùrsa! Thèid thu a leigeil a-steach ma tha iad a' freagairt gu math.",
"@knockDesc": {
"type": "String",
"placeholders": {}
},
"joinSpaceOnboardingDesc": "¿Tienes un código de invitación o un enlace a un curso público?",
"welcomeUser": "Bienvenido {user}",
"@joinSpaceOnboardingDesc": {
"type": "String",
"placeholders": {}
},
"@welcomeUser": {
"type": "String",
"placeholders": {
"user": {
"type": "String"
}
}
},
"publicInviteDescChat": "Cuir fiosrúcháin ar úsáideoirí chun iad a gcuir isteach sa chomhrá seo.",
"publicInviteDescSpace": "Cuir fiosrúcháin ar úsáideoirí chun iad a gcuir isteach sa spás seo.",
"@publicInviteDescChat": {
"type": "String",
"placeholders": {}
},
"@publicInviteDescSpace": {
"type": "String",
"placeholders": {}
}
}

View file

@ -1,5 +1,5 @@
{
"@@last_modified": "2026-01-16 14:32:53.055805",
"@@last_modified": "2026-01-22 12:01:27.366543",
"about": "Informazioni",
"@about": {
"type": "String",
@ -10868,5 +10868,64 @@
"@constructUseIncGCDesc": {
"type": "String",
"placeholders": {}
},
"constructUseCorGEDesc": "Pratica degli errori grammaticali corretti",
"constructUseIncGEDesc": "Pratica degli errori grammaticali scorretti",
"fillInBlank": "Compila lo spazio vuoto con la scelta corretta",
"learn": "Impara",
"languageUpdated": "Lingua target aggiornata!",
"@constructUseCorGEDesc": {
"type": "String",
"placeholders": {}
},
"@constructUseIncGEDesc": {
"type": "String",
"placeholders": {}
},
"@fillInBlank": {
"type": "String",
"placeholders": {}
},
"@learn": {
"type": "String",
"placeholders": {}
},
"@languageUpdated": {
"type": "String",
"placeholders": {}
},
"voiceDropdownTitle": "Voce del bot Pangea",
"@voiceDropdownTitle": {
"type": "String",
"placeholders": {}
},
"knockDesc": "La tua richiesta è stata inviata all'amministratore del corso! Sarai ammesso se approvano.",
"@knockDesc": {
"type": "String",
"placeholders": {}
},
"joinSpaceOnboardingDesc": "Hai un codice di invito o un link per un corso pubblico?",
"welcomeUser": "Benvenuto {user}",
"@joinSpaceOnboardingDesc": {
"type": "String",
"placeholders": {}
},
"@welcomeUser": {
"type": "String",
"placeholders": {
"user": {
"type": "String"
}
}
},
"publicInviteDescChat": "Cerca utenti per invitarli a questa chat.",
"publicInviteDescSpace": "Cerca utenti per invitarli a questo spazio.",
"@publicInviteDescChat": {
"type": "String",
"placeholders": {}
},
"@publicInviteDescSpace": {
"type": "String",
"placeholders": {}
}
}

View file

@ -1,6 +1,6 @@
{
"@@locale": "ja",
"@@last_modified": "2026-01-16 14:34:03.101722",
"@@last_modified": "2026-01-22 12:02:12.048814",
"about": "このアプリについて",
"@about": {
"type": "String",
@ -11655,5 +11655,64 @@
"@constructUseIncGCDesc": {
"type": "String",
"placeholders": {}
},
"constructUseCorGEDesc": "文法エラーの正しい練習",
"constructUseIncGEDesc": "文法エラーの不正確な練習",
"fillInBlank": "正しい選択肢で空欄を埋めてください",
"learn": "学ぶ",
"languageUpdated": "ターゲット言語が更新されました!",
"@constructUseCorGEDesc": {
"type": "String",
"placeholders": {}
},
"@constructUseIncGEDesc": {
"type": "String",
"placeholders": {}
},
"@fillInBlank": {
"type": "String",
"placeholders": {}
},
"@learn": {
"type": "String",
"placeholders": {}
},
"@languageUpdated": {
"type": "String",
"placeholders": {}
},
"voiceDropdownTitle": "パンゲアボットの声",
"@voiceDropdownTitle": {
"type": "String",
"placeholders": {}
},
"knockDesc": "あなたのリクエストはコース管理者に送信されました! 彼らが承認すれば、入ることができます。",
"@knockDesc": {
"type": "String",
"placeholders": {}
},
"joinSpaceOnboardingDesc": "招待コードまたは公開コースへのリンクはありますか?",
"welcomeUser": "ようこそ {user} さん",
"@joinSpaceOnboardingDesc": {
"type": "String",
"placeholders": {}
},
"@welcomeUser": {
"type": "String",
"placeholders": {
"user": {
"type": "String"
}
}
},
"publicInviteDescChat": "このチャットに招待するユーザーを検索します。",
"publicInviteDescSpace": "このスペースに招待するユーザーを検索します。",
"@publicInviteDescChat": {
"type": "String",
"placeholders": {}
},
"@publicInviteDescSpace": {
"type": "String",
"placeholders": {}
}
}

View file

@ -2594,7 +2594,7 @@
"playWithAI": "ამ დროისთვის ითამაშეთ AI-თან",
"courseStartDesc": "Pangea Bot მზადაა ნებისმიერ დროს გასასვლელად!\n\n...მაგრამ სწავლა უკეთესია მეგობრებთან ერთად!",
"@@locale": "ka",
"@@last_modified": "2026-01-16 14:34:13.710408",
"@@last_modified": "2026-01-22 12:02:18.860564",
"@alwaysUse24HourFormat": {
"type": "String",
"placeholders": {}
@ -11895,5 +11895,64 @@
"@constructUseIncGCDesc": {
"type": "String",
"placeholders": {}
},
"constructUseCorGEDesc": "მართებული გრამატიკული შეცდომების პრაქტიკა",
"constructUseIncGEDesc": "არასწორი გრამატიკული შეცდომების პრაქტიკა",
"fillInBlank": "შეავსეთ ცარიელი ადგილი სწორი არჩევანით",
"learn": "სწავლა",
"languageUpdated": "მიზნობრივი ენა განახლებულია!",
"@constructUseCorGEDesc": {
"type": "String",
"placeholders": {}
},
"@constructUseIncGEDesc": {
"type": "String",
"placeholders": {}
},
"@fillInBlank": {
"type": "String",
"placeholders": {}
},
"@learn": {
"type": "String",
"placeholders": {}
},
"@languageUpdated": {
"type": "String",
"placeholders": {}
},
"voiceDropdownTitle": "პანჯეა ბოტის ხმა",
"@voiceDropdownTitle": {
"type": "String",
"placeholders": {}
},
"knockDesc": "თქვენი მოთხოვნა გაგზავნილია კურსის ადმინისტრატორთან! თქვენ შეგიშვებენ, თუ ისინი დაამტკიცებენ.",
"@knockDesc": {
"type": "String",
"placeholders": {}
},
"joinSpaceOnboardingDesc": "გაქვთ თუ არა მოწვევის კოდი ან ბმული საჯარო კურსზე?",
"welcomeUser": "კეთილი იყოს თქვენი მობრძანება {user}",
"@joinSpaceOnboardingDesc": {
"type": "String",
"placeholders": {}
},
"@welcomeUser": {
"type": "String",
"placeholders": {
"user": {
"type": "String"
}
}
},
"publicInviteDescChat": "მომხმარებლების ძიება, რათა მათ ამ ჩატში მოიწვიოთ.",
"publicInviteDescSpace": "მომხმარებლების ძიება, რათა მათ ამ სივრცეში მოიწვიოთ.",
"@publicInviteDescChat": {
"type": "String",
"placeholders": {}
},
"@publicInviteDescSpace": {
"type": "String",
"placeholders": {}
}
}

View file

@ -1,5 +1,5 @@
{
"@@last_modified": "2026-01-16 14:31:56.042093",
"@@last_modified": "2026-01-22 12:00:49.883642",
"about": "소개",
"@about": {
"type": "String",
@ -10973,5 +10973,64 @@
"@constructUseIncGCDesc": {
"type": "String",
"placeholders": {}
},
"constructUseCorGEDesc": "문법 오류 수정 연습",
"constructUseIncGEDesc": "문법 오류 비정상 연습",
"fillInBlank": "올바른 선택으로 빈칸을 채우세요",
"learn": "배우다",
"languageUpdated": "목표 언어가 업데이트되었습니다!",
"@constructUseCorGEDesc": {
"type": "String",
"placeholders": {}
},
"@constructUseIncGEDesc": {
"type": "String",
"placeholders": {}
},
"@fillInBlank": {
"type": "String",
"placeholders": {}
},
"@learn": {
"type": "String",
"placeholders": {}
},
"@languageUpdated": {
"type": "String",
"placeholders": {}
},
"voiceDropdownTitle": "판게아 봇 음성",
"@voiceDropdownTitle": {
"type": "String",
"placeholders": {}
},
"knockDesc": "귀하의 요청이 과정 관리자에게 전송되었습니다! 그들이 승인하면 들어갈 수 있습니다.",
"@knockDesc": {
"type": "String",
"placeholders": {}
},
"joinSpaceOnboardingDesc": "공개 과정에 대한 초대 코드나 링크가 있습니까?",
"welcomeUser": "환영합니다 {user}",
"@joinSpaceOnboardingDesc": {
"type": "String",
"placeholders": {}
},
"@welcomeUser": {
"type": "String",
"placeholders": {
"user": {
"type": "String"
}
}
},
"publicInviteDescChat": "이 채팅에 초대할 사용자를 검색하세요.",
"publicInviteDescSpace": "이 공간에 초대할 사용자를 검색하세요.",
"@publicInviteDescChat": {
"type": "String",
"placeholders": {}
},
"@publicInviteDescSpace": {
"type": "String",
"placeholders": {}
}
}

View file

@ -3861,7 +3861,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-01-16 14:33:32.087889",
"@@last_modified": "2026-01-22 12:01:53.612206",
"@alwaysUse24HourFormat": {
"type": "String",
"placeholders": {}
@ -11670,5 +11670,64 @@
"@constructUseIncGCDesc": {
"type": "String",
"placeholders": {}
},
"constructUseCorGEDesc": "Teisingos gramatikos klaidų praktika",
"constructUseIncGEDesc": "Neteisingos gramatikos klaidų praktika",
"fillInBlank": "Užpildykite tuščią vietą teisingu pasirinkimu",
"learn": "Mokytis",
"languageUpdated": "Tikslo kalba atnaujinta!",
"@constructUseCorGEDesc": {
"type": "String",
"placeholders": {}
},
"@constructUseIncGEDesc": {
"type": "String",
"placeholders": {}
},
"@fillInBlank": {
"type": "String",
"placeholders": {}
},
"@learn": {
"type": "String",
"placeholders": {}
},
"@languageUpdated": {
"type": "String",
"placeholders": {}
},
"voiceDropdownTitle": "Pangea Bot balsas",
"@voiceDropdownTitle": {
"type": "String",
"placeholders": {}
},
"knockDesc": "Jūsų prašymas buvo išsiųstas kurso administratoriui! Būsite įleistas, jei jie patvirtins.",
"@knockDesc": {
"type": "String",
"placeholders": {}
},
"joinSpaceOnboardingDesc": "Ar turite kvietimo kodą arba nuorodą į viešą kursą?",
"welcomeUser": "Sveiki atvykę, {user}",
"@joinSpaceOnboardingDesc": {
"type": "String",
"placeholders": {}
},
"@welcomeUser": {
"type": "String",
"placeholders": {
"user": {
"type": "String"
}
}
},
"publicInviteDescChat": "Ieškokite vartotojų, kad juos pakviestumėte į šį pokalbį.",
"publicInviteDescSpace": "Ieškokite vartotojų, kad juos pakviestumėte į šią erdvę.",
"@publicInviteDescChat": {
"type": "String",
"placeholders": {}
},
"@publicInviteDescSpace": {
"type": "String",
"placeholders": {}
}
}

View file

@ -4482,7 +4482,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-01-16 14:33:21.744947",
"@@last_modified": "2026-01-22 12:01:46.451812",
"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",
@ -10851,5 +10851,64 @@
"@constructUseIncGCDesc": {
"type": "String",
"placeholders": {}
},
"constructUseCorGEDesc": "Pareiza gramatikas kļūdu prakse",
"constructUseIncGEDesc": "Nepareiza gramatikas kļūdu prakse",
"fillInBlank": "Aizpildiet tukšo vietu ar pareizo izvēli",
"learn": "Mācīties",
"languageUpdated": "Mērķa valoda atjaunota!",
"@constructUseCorGEDesc": {
"type": "String",
"placeholders": {}
},
"@constructUseIncGEDesc": {
"type": "String",
"placeholders": {}
},
"@fillInBlank": {
"type": "String",
"placeholders": {}
},
"@learn": {
"type": "String",
"placeholders": {}
},
"@languageUpdated": {
"type": "String",
"placeholders": {}
},
"voiceDropdownTitle": "Pangea Bot balss",
"@voiceDropdownTitle": {
"type": "String",
"placeholders": {}
},
"knockDesc": "Jūsu pieprasījums ir nosūtīts kursa administratoram! Jūs tiksiet iekšā, ja viņi apstiprinās.",
"@knockDesc": {
"type": "String",
"placeholders": {}
},
"joinSpaceOnboardingDesc": "Vai jums ir uzaicinājuma kods vai saite uz publisku kursu?",
"welcomeUser": "Laipni lūdzam, {user}",
"@joinSpaceOnboardingDesc": {
"type": "String",
"placeholders": {}
},
"@welcomeUser": {
"type": "String",
"placeholders": {
"user": {
"type": "String"
}
}
},
"publicInviteDescChat": "Meklējiet lietotājus, lai viņus aicinātu uz šo čatu.",
"publicInviteDescSpace": "Meklējiet lietotājus, lai viņus aicinātu uz šo telpu.",
"@publicInviteDescChat": {
"type": "String",
"placeholders": {}
},
"@publicInviteDescSpace": {
"type": "String",
"placeholders": {}
}
}

View file

@ -1,5 +1,5 @@
{
"@@last_modified": "2026-01-16 14:33:02.881359",
"@@last_modified": "2026-01-22 12:01:31.934487",
"about": "Om",
"@about": {
"type": "String",
@ -11958,5 +11958,64 @@
"@constructUseIncGCDesc": {
"type": "String",
"placeholders": {}
},
"constructUseCorGEDesc": "Korrekt grammatikkfeil praksis",
"constructUseIncGEDesc": "Feil grammatikkfeil praksis",
"fillInBlank": "Fyll inn blanketten med riktig valg",
"learn": "Lær",
"languageUpdated": "Mål språk oppdatert!",
"@constructUseCorGEDesc": {
"type": "String",
"placeholders": {}
},
"@constructUseIncGEDesc": {
"type": "String",
"placeholders": {}
},
"@fillInBlank": {
"type": "String",
"placeholders": {}
},
"@learn": {
"type": "String",
"placeholders": {}
},
"@languageUpdated": {
"type": "String",
"placeholders": {}
},
"voiceDropdownTitle": "Pangea Bot-stemme",
"@voiceDropdownTitle": {
"type": "String",
"placeholders": {}
},
"knockDesc": "Din forespørsel har blitt sendt til kursadministratoren! Du vil bli sluppet inn hvis de godkjenner.",
"@knockDesc": {
"type": "String",
"placeholders": {}
},
"joinSpaceOnboardingDesc": "Har du en invitasjonskode eller lenke til et offentlig kurs?",
"welcomeUser": "Velkommen {user}",
"@joinSpaceOnboardingDesc": {
"type": "String",
"placeholders": {}
},
"@welcomeUser": {
"type": "String",
"placeholders": {
"user": {
"type": "String"
}
}
},
"publicInviteDescChat": "Søk etter brukere for å invitere dem til denne chatten.",
"publicInviteDescSpace": "Søk etter brukere for å invitere dem til dette rommet.",
"@publicInviteDescChat": {
"type": "String",
"placeholders": {}
},
"@publicInviteDescSpace": {
"type": "String",
"placeholders": {}
}
}

View file

@ -1,5 +1,5 @@
{
"@@last_modified": "2026-01-16 14:33:40.784777",
"@@last_modified": "2026-01-22 12:01:58.109571",
"about": "Over ons",
"@about": {
"type": "String",
@ -10865,5 +10865,64 @@
"@constructUseIncGCDesc": {
"type": "String",
"placeholders": {}
},
"constructUseCorGEDesc": "Oefening voor correcte grammatica",
"constructUseIncGEDesc": "Oefening voor onjuiste grammatica",
"fillInBlank": "Vul de lege ruimte in met de juiste keuze",
"learn": "Leren",
"languageUpdated": "Doeltaal bijgewerkt!",
"@constructUseCorGEDesc": {
"type": "String",
"placeholders": {}
},
"@constructUseIncGEDesc": {
"type": "String",
"placeholders": {}
},
"@fillInBlank": {
"type": "String",
"placeholders": {}
},
"@learn": {
"type": "String",
"placeholders": {}
},
"@languageUpdated": {
"type": "String",
"placeholders": {}
},
"voiceDropdownTitle": "Pangea Bot stem",
"@voiceDropdownTitle": {
"type": "String",
"placeholders": {}
},
"knockDesc": "Je verzoek is verzonden naar de cursusbeheerder! Je wordt toegelaten als ze goedkeuren.",
"@knockDesc": {
"type": "String",
"placeholders": {}
},
"joinSpaceOnboardingDesc": "Heb je een uitnodigingscode of link naar een openbare cursus?",
"welcomeUser": "Welkom {user}",
"@joinSpaceOnboardingDesc": {
"type": "String",
"placeholders": {}
},
"@welcomeUser": {
"type": "String",
"placeholders": {
"user": {
"type": "String"
}
}
},
"publicInviteDescChat": "Zoek naar gebruikers om ze uit te nodigen voor deze chat.",
"publicInviteDescSpace": "Zoek naar gebruikers om ze uit te nodigen voor deze ruimte.",
"@publicInviteDescChat": {
"type": "String",
"placeholders": {}
},
"@publicInviteDescSpace": {
"type": "String",
"placeholders": {}
}
}

View file

@ -1,6 +1,6 @@
{
"@@locale": "pl",
"@@last_modified": "2026-01-16 14:33:54.131854",
"@@last_modified": "2026-01-22 12:02:06.402870",
"about": "O aplikacji",
"@about": {
"type": "String",
@ -10863,5 +10863,64 @@
"@constructUseIncGCDesc": {
"type": "String",
"placeholders": {}
},
"constructUseCorGEDesc": "Ćwiczenie poprawnych błędów gramatycznych",
"constructUseIncGEDesc": "Ćwiczenie niepoprawnych błędów gramatycznych",
"fillInBlank": "Uzupełnij lukę poprawnym wyborem",
"learn": "Ucz się",
"languageUpdated": "Język docelowy zaktualizowany!",
"@constructUseCorGEDesc": {
"type": "String",
"placeholders": {}
},
"@constructUseIncGEDesc": {
"type": "String",
"placeholders": {}
},
"@fillInBlank": {
"type": "String",
"placeholders": {}
},
"@learn": {
"type": "String",
"placeholders": {}
},
"@languageUpdated": {
"type": "String",
"placeholders": {}
},
"voiceDropdownTitle": "Głos bota Pangea",
"@voiceDropdownTitle": {
"type": "String",
"placeholders": {}
},
"knockDesc": "Twoja prośba została wysłana do administratora kursu! Zostaniesz wpuszczony, jeśli ją zatwierdzą.",
"@knockDesc": {
"type": "String",
"placeholders": {}
},
"joinSpaceOnboardingDesc": "Czy masz kod zaproszenia lub link do publicznego kursu?",
"welcomeUser": "Witaj {user}",
"@joinSpaceOnboardingDesc": {
"type": "String",
"placeholders": {}
},
"@welcomeUser": {
"type": "String",
"placeholders": {
"user": {
"type": "String"
}
}
},
"publicInviteDescChat": "Szukaj użytkowników, aby zaprosić ich do tej rozmowy.",
"publicInviteDescSpace": "Szukaj użytkowników, aby zaprosić ich do tej przestrzeni.",
"@publicInviteDescChat": {
"type": "String",
"placeholders": {}
},
"@publicInviteDescSpace": {
"type": "String",
"placeholders": {}
}
}

View file

@ -1,5 +1,5 @@
{
"@@last_modified": "2026-01-16 14:32:38.304072",
"@@last_modified": "2026-01-22 12:01:18.883594",
"copiedToClipboard": "Copiada para a área de transferência",
"@copiedToClipboard": {
"type": "String",
@ -11965,5 +11965,64 @@
"@constructUseIncGCDesc": {
"type": "String",
"placeholders": {}
},
"constructUseCorGEDesc": "Prática de erro gramatical correto",
"constructUseIncGEDesc": "Prática de erro gramatical incorreto",
"fillInBlank": "Preencha a lacuna com a escolha correta",
"learn": "Aprender",
"languageUpdated": "Idioma de destino atualizado!",
"@constructUseCorGEDesc": {
"type": "String",
"placeholders": {}
},
"@constructUseIncGEDesc": {
"type": "String",
"placeholders": {}
},
"@fillInBlank": {
"type": "String",
"placeholders": {}
},
"@learn": {
"type": "String",
"placeholders": {}
},
"@languageUpdated": {
"type": "String",
"placeholders": {}
},
"voiceDropdownTitle": "Voz do Bot Pangea",
"@voiceDropdownTitle": {
"type": "String",
"placeholders": {}
},
"knockDesc": "Sua solicitação foi enviada ao administrador do curso! Você será admitido se eles aprovarem.",
"@knockDesc": {
"type": "String",
"placeholders": {}
},
"joinSpaceOnboardingDesc": "Você tem um código de convite ou link para um curso público?",
"welcomeUser": "Bem-vindo {user}",
"@joinSpaceOnboardingDesc": {
"type": "String",
"placeholders": {}
},
"@welcomeUser": {
"type": "String",
"placeholders": {
"user": {
"type": "String"
}
}
},
"publicInviteDescChat": "Procure usuários para convidá-los para este chat.",
"publicInviteDescSpace": "Procure usuários para convidá-los para este espaço.",
"@publicInviteDescChat": {
"type": "String",
"placeholders": {}
},
"@publicInviteDescSpace": {
"type": "String",
"placeholders": {}
}
}

View file

@ -1,5 +1,5 @@
{
"@@last_modified": "2026-01-16 14:32:33.894131",
"@@last_modified": "2026-01-22 12:01:15.782911",
"about": "Sobre",
"@about": {
"type": "String",
@ -11223,5 +11223,64 @@
"@constructUseIncGCDesc": {
"type": "String",
"placeholders": {}
},
"constructUseCorGEDesc": "Prática de erro gramatical correto",
"constructUseIncGEDesc": "Prática de erro gramatical incorreto",
"fillInBlank": "Preencha a lacuna com a escolha correta",
"learn": "Aprender",
"languageUpdated": "Idioma de destino atualizado!",
"@constructUseCorGEDesc": {
"type": "String",
"placeholders": {}
},
"@constructUseIncGEDesc": {
"type": "String",
"placeholders": {}
},
"@fillInBlank": {
"type": "String",
"placeholders": {}
},
"@learn": {
"type": "String",
"placeholders": {}
},
"@languageUpdated": {
"type": "String",
"placeholders": {}
},
"voiceDropdownTitle": "Voz do Bot Pangea",
"@voiceDropdownTitle": {
"type": "String",
"placeholders": {}
},
"knockDesc": "Sua solicitação foi enviada ao administrador do curso! Você será admitido se eles aprovarem.",
"@knockDesc": {
"type": "String",
"placeholders": {}
},
"joinSpaceOnboardingDesc": "Você tem um código de convite ou link para um curso público?",
"welcomeUser": "Bem-vindo {user}",
"@joinSpaceOnboardingDesc": {
"type": "String",
"placeholders": {}
},
"@welcomeUser": {
"type": "String",
"placeholders": {
"user": {
"type": "String"
}
}
},
"publicInviteDescChat": "Procure usuários para convidá-los para este chat.",
"publicInviteDescSpace": "Procure usuários para convidá-los para este espaço.",
"@publicInviteDescChat": {
"type": "String",
"placeholders": {}
},
"@publicInviteDescSpace": {
"type": "String",
"placeholders": {}
}
}

View file

@ -3331,7 +3331,7 @@
"selectAll": "Selecionar tudo",
"deselectAll": "Desmarcar tudo",
"@@locale": "pt_PT",
"@@last_modified": "2026-01-16 14:33:14.369639",
"@@last_modified": "2026-01-22 12:01:38.348686",
"@alwaysUse24HourFormat": {
"type": "String",
"placeholders": {}
@ -11894,5 +11894,64 @@
"@constructUseIncGCDesc": {
"type": "String",
"placeholders": {}
},
"constructUseCorGEDesc": "Prática de erro gramatical correto",
"constructUseIncGEDesc": "Prática de erro gramatical incorreto",
"fillInBlank": "Preencha a lacuna com a escolha correta",
"learn": "Aprender",
"languageUpdated": "Idioma de destino atualizado!",
"@constructUseCorGEDesc": {
"type": "String",
"placeholders": {}
},
"@constructUseIncGEDesc": {
"type": "String",
"placeholders": {}
},
"@fillInBlank": {
"type": "String",
"placeholders": {}
},
"@learn": {
"type": "String",
"placeholders": {}
},
"@languageUpdated": {
"type": "String",
"placeholders": {}
},
"voiceDropdownTitle": "Voz do Bot Pangea",
"@voiceDropdownTitle": {
"type": "String",
"placeholders": {}
},
"knockDesc": "Sua solicitação foi enviada ao administrador do curso! Você será admitido se eles aprovarem.",
"@knockDesc": {
"type": "String",
"placeholders": {}
},
"joinSpaceOnboardingDesc": "Você tem um código de convite ou link para um curso público?",
"welcomeUser": "Bem-vindo {user}",
"@joinSpaceOnboardingDesc": {
"type": "String",
"placeholders": {}
},
"@welcomeUser": {
"type": "String",
"placeholders": {
"user": {
"type": "String"
}
}
},
"publicInviteDescChat": "Procure usuários para convidá-los para este chat.",
"publicInviteDescSpace": "Procure usuários para convidá-los para este espaço.",
"@publicInviteDescChat": {
"type": "String",
"placeholders": {}
},
"@publicInviteDescSpace": {
"type": "String",
"placeholders": {}
}
}

View file

@ -1,5 +1,5 @@
{
"@@last_modified": "2026-01-16 14:32:16.042822",
"@@last_modified": "2026-01-22 12:01:04.721303",
"about": "Despre",
"@about": {
"type": "String",
@ -11600,5 +11600,64 @@
"@constructUseIncGCDesc": {
"type": "String",
"placeholders": {}
},
"constructUseCorGEDesc": "Practică corectă a erorilor de gramatică",
"constructUseIncGEDesc": "Practică incorectă a erorilor de gramatică",
"fillInBlank": "Completați spațiul gol cu alegerea corectă",
"learn": "Învățați",
"languageUpdated": "Limba țintă a fost actualizată!",
"@constructUseCorGEDesc": {
"type": "String",
"placeholders": {}
},
"@constructUseIncGEDesc": {
"type": "String",
"placeholders": {}
},
"@fillInBlank": {
"type": "String",
"placeholders": {}
},
"@learn": {
"type": "String",
"placeholders": {}
},
"@languageUpdated": {
"type": "String",
"placeholders": {}
},
"voiceDropdownTitle": "Vocea Pangea Bot",
"@voiceDropdownTitle": {
"type": "String",
"placeholders": {}
},
"knockDesc": "Cererea ta a fost trimisă administratorului cursului! Vei fi lăsat să intri dacă ei aprobă.",
"@knockDesc": {
"type": "String",
"placeholders": {}
},
"joinSpaceOnboardingDesc": "Ai un cod de invitație sau un link pentru un curs public?",
"welcomeUser": "Bine ai venit {user}",
"@joinSpaceOnboardingDesc": {
"type": "String",
"placeholders": {}
},
"@welcomeUser": {
"type": "String",
"placeholders": {
"user": {
"type": "String"
}
}
},
"publicInviteDescChat": "Caută utilizatori pentru a-i invita în acest chat.",
"publicInviteDescSpace": "Caută utilizatori pentru a-i invita în acest spațiu.",
"@publicInviteDescChat": {
"type": "String",
"placeholders": {}
},
"@publicInviteDescSpace": {
"type": "String",
"placeholders": {}
}
}

View file

@ -1,6 +1,6 @@
{
"@@locale": "ru",
"@@last_modified": "2026-01-16 14:34:10.739852",
"@@last_modified": "2026-01-22 12:02:17.097388",
"about": "О проекте",
"@about": {
"type": "String",
@ -3132,8 +3132,6 @@
"@invalidUrl": {},
"addLink": "Добавить ссылку",
"@addLink": {},
"italicText": "Italic",
"@italicText": {},
"unableToJoinChat": "Невозможно присоединиться к чату. Возможно, другая сторона уже закончила разговор.",
"@unableToJoinChat": {},
"serverLimitReached": "Ограничения сервера. Ожидайте{seconds} секунд...",
@ -3689,13 +3687,6 @@
"acceptSelection": "Принять исправление",
"why": "Почему?",
"definition": "Определение",
"exampleSentence": "Приклад речення",
"reportToTeacher": "Кому ви хочете повідомити про це повідомлення?",
"reportMessageTitle": "{reportingUserId} повідомив про повідомлення від {reportedUserId} у чаті {roomName}",
"reportMessageBody": "Повідомлення: {reportedMessage}\nПричина: {reason}",
"noTeachersFound": "Вчителі не знайдені для повідомлення",
"trialExpiration": "Ваша безкоштовна пробна версія закінчується {expiration}",
"freeTrialDesc": "Нові користувачі отримують тижневу безкоштовну пробну версію Pangea Chat",
"activateTrial": "Безкоштовна 7-денна пробна версія",
"successfullySubscribed": "Ви успішно підписалися!",
"clickToManageSubscription": "Натисніть тут, щоб керувати підпискою.",
@ -4078,13 +4069,6 @@
"constructUseIncMDesc": "Некорректно в деятельности по грамматике",
"constructUseIgnMDesc": "Игнорируется в деятельности по грамматике",
"constructUseEmojiDesc": "Правильно в деятельности по эмодзи",
"constructUseCollected": "Thu thập trong trò chuyện",
"constructUseNanDesc": "Không áp dụng được",
"xpIntoLevel": "{currentXP} / {maxXP} XP",
"enableTTSToolName": "Bật chuyển đổi văn bản thành giọng nói",
"enableTTSToolDescription": "Cho phép ứng dụng tạo ra đầu ra chuyển đổi văn bản thành giọng nói cho các phần của văn bản bằng ngôn ngữ mục tiêu của bạn.",
"yourUsername": "Tên người dùng của bạn",
"yourEmail": "Email của bạn",
"iWantToLearn": "Я хочу учиться",
"pleaseEnterEmail": "Пожалуйста, введите действительный адрес электронной почты.",
"myBaseLanguage": "Мой основной язык",
@ -10970,5 +10954,83 @@
"@constructUseIncGCDesc": {
"type": "String",
"placeholders": {}
},
"constructUseCorGEDesc": "Практика исправления грамматических ошибок",
"constructUseIncGEDesc": "Практика неправильных грамматических ошибок",
"fillInBlank": "Заполните пропуск правильным вариантом",
"learn": "Учить",
"languageUpdated": "Целевой язык обновлен!",
"@constructUseCorGEDesc": {
"type": "String",
"placeholders": {}
},
"@constructUseIncGEDesc": {
"type": "String",
"placeholders": {}
},
"@fillInBlank": {
"type": "String",
"placeholders": {}
},
"@learn": {
"type": "String",
"placeholders": {}
},
"@languageUpdated": {
"type": "String",
"placeholders": {}
},
"voiceDropdownTitle": "Голос бота Pangea",
"@voiceDropdownTitle": {
"type": "String",
"placeholders": {}
},
"knockDesc": "Ваш запрос отправлен администратору курса! Вы будете допущены, если они одобрят.",
"@knockDesc": {
"type": "String",
"placeholders": {}
},
"italicText": "Курсивный текст",
"exampleSentence": "Пример предложения",
"reportToTeacher": "Кому вы хотите пожаловаться на это сообщение?",
"reportMessageTitle": "{reportingUserId} пожаловался на сообщение от {reportedUserId} в чате {roomName}",
"reportMessageBody": "Сообщение: {reportedMessage}\nПричина: {reason}",
"noTeachersFound": "Учителя для жалобы не найдены",
"trialExpiration": "Ваш бесплатный пробный период истекает {expiration}",
"freeTrialDesc": "Новые пользователи получают бесплатную пробную неделю в Pangea Chat",
"constructUseCollected": "Собрано в чате",
"constructUseNanDesc": "Не применимо",
"xpIntoLevel": "{currentXP} / {maxXP} XP",
"enableTTSToolName": "Включен преобразователь текста в речь",
"enableTTSToolDescription": "Позволяет приложению генерировать озвучивание текста на вашем целевом языке.",
"yourUsername": "Ваше имя пользователя",
"yourEmail": "Ваш email",
"@italicText": {
"type": "String",
"placeholders": {}
},
"joinSpaceOnboardingDesc": "У вас есть код приглашения или ссылка на публичный курс?",
"welcomeUser": "Добро пожаловать {user}",
"@joinSpaceOnboardingDesc": {
"type": "String",
"placeholders": {}
},
"@welcomeUser": {
"type": "String",
"placeholders": {
"user": {
"type": "String"
}
}
},
"publicInviteDescChat": "Ищите пользователей, чтобы пригласить их в этот чат.",
"publicInviteDescSpace": "Ищите пользователей, чтобы пригласить их в это пространство.",
"@publicInviteDescChat": {
"type": "String",
"placeholders": {}
},
"@publicInviteDescSpace": {
"type": "String",
"placeholders": {}
}
}

View file

@ -1,6 +1,6 @@
{
"@@locale": "sk",
"@@last_modified": "2026-01-16 14:32:18.425596",
"@@last_modified": "2026-01-22 12:01:06.427460",
"about": "O aplikácii",
"@about": {
"type": "String",
@ -11949,5 +11949,64 @@
"@constructUseIncGCDesc": {
"type": "String",
"placeholders": {}
},
"constructUseCorGEDesc": "Cvičenie na opravu gramatických chýb",
"constructUseIncGEDesc": "Cvičenie na nesprávne gramatické chyby",
"fillInBlank": "Doplňte prázdne miesto správnou voľbou",
"learn": "Učte sa",
"languageUpdated": "Cieľový jazyk bol aktualizovaný!",
"@constructUseCorGEDesc": {
"type": "String",
"placeholders": {}
},
"@constructUseIncGEDesc": {
"type": "String",
"placeholders": {}
},
"@fillInBlank": {
"type": "String",
"placeholders": {}
},
"@learn": {
"type": "String",
"placeholders": {}
},
"@languageUpdated": {
"type": "String",
"placeholders": {}
},
"voiceDropdownTitle": "Hlas Pangea Bota",
"@voiceDropdownTitle": {
"type": "String",
"placeholders": {}
},
"knockDesc": "Vaša žiadosť bola odoslaná administrátorovi kurzu! Budete vpustený, ak ju schvália.",
"@knockDesc": {
"type": "String",
"placeholders": {}
},
"joinSpaceOnboardingDesc": "Máte pozývací kód alebo odkaz na verejný kurz?",
"welcomeUser": "Vitaj {user}",
"@joinSpaceOnboardingDesc": {
"type": "String",
"placeholders": {}
},
"@welcomeUser": {
"type": "String",
"placeholders": {
"user": {
"type": "String"
}
}
},
"publicInviteDescChat": "Hľadajte používateľov, aby ste ich pozvali do tohto chatu.",
"publicInviteDescSpace": "Hľadajte používateľov, aby ste ich pozvali do tohto priestoru.",
"@publicInviteDescChat": {
"type": "String",
"placeholders": {}
},
"@publicInviteDescSpace": {
"type": "String",
"placeholders": {}
}
}

View file

@ -2464,7 +2464,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-01-16 14:32:46.504404",
"@@last_modified": "2026-01-22 12:01:24.219945",
"@alwaysUse24HourFormat": {
"type": "String",
"placeholders": {}
@ -11946,5 +11946,64 @@
"@constructUseIncGCDesc": {
"type": "String",
"placeholders": {}
},
"constructUseCorGEDesc": "Praksa pravilne rabe slovnice",
"constructUseIncGEDesc": "Praksa nepravilne rabe slovnice",
"fillInBlank": "Izpolnite prazno mesto s pravilno izbiro",
"learn": "Učite se",
"languageUpdated": "Ciljni jezik je posodobljen!",
"@constructUseCorGEDesc": {
"type": "String",
"placeholders": {}
},
"@constructUseIncGEDesc": {
"type": "String",
"placeholders": {}
},
"@fillInBlank": {
"type": "String",
"placeholders": {}
},
"@learn": {
"type": "String",
"placeholders": {}
},
"@languageUpdated": {
"type": "String",
"placeholders": {}
},
"voiceDropdownTitle": "Glas Pangea Bota",
"@voiceDropdownTitle": {
"type": "String",
"placeholders": {}
},
"knockDesc": "Vaša zahteva je bila poslana skrbniku tečaja! Vstopili boste, če jo odobrijo.",
"@knockDesc": {
"type": "String",
"placeholders": {}
},
"joinSpaceOnboardingDesc": "Imate kodo za povabilo ali povezavo do javnega tečaja?",
"welcomeUser": "Dobrodošli {user}",
"@joinSpaceOnboardingDesc": {
"type": "String",
"placeholders": {}
},
"@welcomeUser": {
"type": "String",
"placeholders": {
"user": {
"type": "String"
}
}
},
"publicInviteDescChat": "Išči uporabnike, da jih povabiš v ta klepet.",
"publicInviteDescSpace": "Išči uporabnike, da jih povabiš v ta prostor.",
"@publicInviteDescChat": {
"type": "String",
"placeholders": {}
},
"@publicInviteDescSpace": {
"type": "String",
"placeholders": {}
}
}

View file

@ -1,5 +1,5 @@
{
"@@last_modified": "2026-01-16 14:34:16.018548",
"@@last_modified": "2026-01-22 12:02:20.533801",
"about": "О програму",
"@about": {
"type": "String",
@ -11967,5 +11967,64 @@
"@constructUseIncGCDesc": {
"type": "String",
"placeholders": {}
},
"constructUseCorGEDesc": "Vežba ispravne gramatike",
"constructUseIncGEDesc": "Vežba nepravilne gramatike",
"fillInBlank": "Popunite prazno mesto sa ispravnim izborom",
"learn": "Učite",
"languageUpdated": "Ciljni jezik je ažuriran!",
"@constructUseCorGEDesc": {
"type": "String",
"placeholders": {}
},
"@constructUseIncGEDesc": {
"type": "String",
"placeholders": {}
},
"@fillInBlank": {
"type": "String",
"placeholders": {}
},
"@learn": {
"type": "String",
"placeholders": {}
},
"@languageUpdated": {
"type": "String",
"placeholders": {}
},
"voiceDropdownTitle": "Glas Pangea Bota",
"@voiceDropdownTitle": {
"type": "String",
"placeholders": {}
},
"knockDesc": "Vaš zahtev je poslat administratoru kursa! Bićete primljeni ako odobre.",
"@knockDesc": {
"type": "String",
"placeholders": {}
},
"joinSpaceOnboardingDesc": "Imate li pozivni kod ili link za javni kurs?",
"welcomeUser": "Dobrodošli {user}",
"@joinSpaceOnboardingDesc": {
"type": "String",
"placeholders": {}
},
"@welcomeUser": {
"type": "String",
"placeholders": {
"user": {
"type": "String"
}
}
},
"publicInviteDescChat": "Pretražite korisnike da ih pozovete u ovaj čat.",
"publicInviteDescSpace": "Pretražite korisnike da ih pozovete u ovaj prostor.",
"@publicInviteDescChat": {
"type": "String",
"placeholders": {}
},
"@publicInviteDescSpace": {
"type": "String",
"placeholders": {}
}
}

View file

@ -1,5 +1,5 @@
{
"@@last_modified": "2026-01-16 14:33:56.060391",
"@@last_modified": "2026-01-22 12:02:08.064498",
"about": "Om",
"@about": {
"type": "String",
@ -11343,5 +11343,64 @@
"@constructUseIncGCDesc": {
"type": "String",
"placeholders": {}
},
"constructUseCorGEDesc": "Korrekt grammatikfel övning",
"constructUseIncGEDesc": "Inkorrekt grammatikfel övning",
"fillInBlank": "Fyll i det tomma med rätt val",
"learn": "Lär dig",
"languageUpdated": "Målspråk uppdaterat!",
"@constructUseCorGEDesc": {
"type": "String",
"placeholders": {}
},
"@constructUseIncGEDesc": {
"type": "String",
"placeholders": {}
},
"@fillInBlank": {
"type": "String",
"placeholders": {}
},
"@learn": {
"type": "String",
"placeholders": {}
},
"@languageUpdated": {
"type": "String",
"placeholders": {}
},
"voiceDropdownTitle": "Pangea Bot röst",
"@voiceDropdownTitle": {
"type": "String",
"placeholders": {}
},
"knockDesc": "Din begäran har skickats till kursadministratören! Du kommer att släppas in om de godkänner.",
"@knockDesc": {
"type": "String",
"placeholders": {}
},
"joinSpaceOnboardingDesc": "Har du en inbjudningskod eller länk till en offentlig kurs?",
"welcomeUser": "Välkommen {user}",
"@joinSpaceOnboardingDesc": {
"type": "String",
"placeholders": {}
},
"@welcomeUser": {
"type": "String",
"placeholders": {
"user": {
"type": "String"
}
}
},
"publicInviteDescChat": "Sök efter användare för att bjuda in dem till den här chatten.",
"publicInviteDescSpace": "Sök efter användare för att bjuda in dem till det här utrymmet.",
"@publicInviteDescChat": {
"type": "String",
"placeholders": {}
},
"@publicInviteDescSpace": {
"type": "String",
"placeholders": {}
}
}

View file

@ -1,5 +1,5 @@
{
"@@last_modified": "2026-01-16 14:33:37.303345",
"@@last_modified": "2026-01-22 12:01:56.352823",
"acceptedTheInvitation": "👍 {username} அழைப்பை ஏற்றுக்கொண்டது",
"@acceptedTheInvitation": {
"type": "String",
@ -11089,5 +11089,64 @@
"@constructUseIncGCDesc": {
"type": "String",
"placeholders": {}
},
"constructUseCorGEDesc": "சரியான இலக்கண பிழை பயிற்சி",
"constructUseIncGEDesc": "தவறான இலக்கண பிழை பயிற்சி",
"fillInBlank": "சரியான தேர்வுடன் காலியை நிரப்பவும்",
"learn": "கற்றுக்கொள்ளுங்கள்",
"languageUpdated": "இலக்கு மொழி புதுப்பிக்கப்பட்டது!",
"@constructUseCorGEDesc": {
"type": "String",
"placeholders": {}
},
"@constructUseIncGEDesc": {
"type": "String",
"placeholders": {}
},
"@fillInBlank": {
"type": "String",
"placeholders": {}
},
"@learn": {
"type": "String",
"placeholders": {}
},
"@languageUpdated": {
"type": "String",
"placeholders": {}
},
"voiceDropdownTitle": "பாஙேஆ பாட்டின் குரல்",
"@voiceDropdownTitle": {
"type": "String",
"placeholders": {}
},
"knockDesc": "உங்கள் கோரிக்கை பாடம் நிர்வாகிக்கு அனுப்பப்பட்டுள்ளது! அவர்கள் ஒப்புதலளித்தால் நீங்கள் உள்ளே அனுமதிக்கப்படுவீர்கள்.",
"@knockDesc": {
"type": "String",
"placeholders": {}
},
"joinSpaceOnboardingDesc": "உங்களுக்கு ஒரு அழைப்பு குறியீடு அல்லது பொது பாடத்திற்கு இணைப்பு உள்ளதா?",
"welcomeUser": "வரவேற்கிறேன் {user}",
"@joinSpaceOnboardingDesc": {
"type": "String",
"placeholders": {}
},
"@welcomeUser": {
"type": "String",
"placeholders": {
"user": {
"type": "String"
}
}
},
"publicInviteDescChat": "இந்த உரையாடலுக்கு அழைக்க பயனர்களை தேடுங்கள்.",
"publicInviteDescSpace": "இந்த இடத்திற்கு அழைக்க பயனர்களை தேடுங்கள்.",
"@publicInviteDescChat": {
"type": "String",
"placeholders": {}
},
"@publicInviteDescSpace": {
"type": "String",
"placeholders": {}
}
}

View file

@ -1920,7 +1920,7 @@
"playWithAI": "ఇప్పుడే AI తో ఆడండి",
"courseStartDesc": "పాంజియా బాట్ ఎప్పుడైనా సిద్ధంగా ఉంటుంది!\n\n...కానీ స్నేహితులతో నేర్చుకోవడం మెరుగైనది!",
"@@locale": "te",
"@@last_modified": "2026-01-16 14:33:29.527681",
"@@last_modified": "2026-01-22 12:01:51.350363",
"@setCustomPermissionLevel": {
"type": "String",
"placeholders": {}
@ -11954,5 +11954,64 @@
"@constructUseIncGCDesc": {
"type": "String",
"placeholders": {}
},
"constructUseCorGEDesc": "సరైన వ్యాకరణ దోషం అభ్యాసం",
"constructUseIncGEDesc": "తప్పు వ్యాకరణ దోషం అభ్యాసం",
"fillInBlank": "సరైన ఎంపికతో ఖాళీని నింపండి",
"learn": "కలవు",
"languageUpdated": "లక్ష్య భాష నవీకరించబడింది!",
"@constructUseCorGEDesc": {
"type": "String",
"placeholders": {}
},
"@constructUseIncGEDesc": {
"type": "String",
"placeholders": {}
},
"@fillInBlank": {
"type": "String",
"placeholders": {}
},
"@learn": {
"type": "String",
"placeholders": {}
},
"@languageUpdated": {
"type": "String",
"placeholders": {}
},
"voiceDropdownTitle": "పాంజియా బాట్ శబ్దం",
"@voiceDropdownTitle": {
"type": "String",
"placeholders": {}
},
"knockDesc": "మీ అభ్యర్థన కోర్సు నిర్వాహకుడికి పంపబడింది! వారు ఆమోదిస్తే, మీరు లోపలికి రానున్నారు.",
"@knockDesc": {
"type": "String",
"placeholders": {}
},
"joinSpaceOnboardingDesc": "మీకు పబ్లిక్ కోర్సుకు ఆహ్వాన కోడ్ లేదా లింక్ ఉందా?",
"welcomeUser": "స్వాగతం {user}",
"@joinSpaceOnboardingDesc": {
"type": "String",
"placeholders": {}
},
"@welcomeUser": {
"type": "String",
"placeholders": {
"user": {
"type": "String"
}
}
},
"publicInviteDescChat": "ఈ చాట్లో ఆహ్వానించడానికి వినియోగదారులను శోధించండి.",
"publicInviteDescSpace": "ఈ స్థలంలో ఆహ్వానించడానికి వినియోగదారులను శోధించండి.",
"@publicInviteDescChat": {
"type": "String",
"placeholders": {}
},
"@publicInviteDescSpace": {
"type": "String",
"placeholders": {}
}
}

View file

@ -4456,7 +4456,7 @@
"playWithAI": "เล่นกับ AI ชั่วคราว",
"courseStartDesc": "Pangea Bot พร้อมที่จะเริ่มต้นได้ทุกเมื่อ!\n\n...แต่การเรียนรู้ดีกว่ากับเพื่อน!",
"@@locale": "th",
"@@last_modified": "2026-01-16 14:33:11.816510",
"@@last_modified": "2026-01-22 12:01:36.919446",
"@alwaysUse24HourFormat": {
"type": "String",
"placeholders": {}
@ -11923,5 +11923,64 @@
"@constructUseIncGCDesc": {
"type": "String",
"placeholders": {}
},
"constructUseCorGEDesc": "การฝึกฝนข้อผิดพลาดทางไวยากรณ์ที่ถูกต้อง",
"constructUseIncGEDesc": "การฝึกฝนข้อผิดพลาดทางไวยากรณ์ที่ไม่ถูกต้อง",
"fillInBlank": "กรอกข้อมูลในช่องว่างด้วยตัวเลือกที่ถูกต้อง",
"learn": "เรียนรู้",
"languageUpdated": "อัปเดตภาษาที่ต้องการแล้ว!",
"@constructUseCorGEDesc": {
"type": "String",
"placeholders": {}
},
"@constructUseIncGEDesc": {
"type": "String",
"placeholders": {}
},
"@fillInBlank": {
"type": "String",
"placeholders": {}
},
"@learn": {
"type": "String",
"placeholders": {}
},
"@languageUpdated": {
"type": "String",
"placeholders": {}
},
"voiceDropdownTitle": "เสียงของ Pangea Bot",
"@voiceDropdownTitle": {
"type": "String",
"placeholders": {}
},
"knockDesc": "คำขอของคุณได้ถูกส่งไปยังผู้ดูแลหลักสูตรแล้ว! คุณจะได้รับอนุญาตให้เข้าหากพวกเขาอนุมัติ.",
"@knockDesc": {
"type": "String",
"placeholders": {}
},
"joinSpaceOnboardingDesc": "คุณมีรหัสเชิญหรือลิงก์ไปยังหลักสูตรสาธารณะหรือไม่?",
"welcomeUser": "ยินดีต้อนรับ {user}",
"@joinSpaceOnboardingDesc": {
"type": "String",
"placeholders": {}
},
"@welcomeUser": {
"type": "String",
"placeholders": {
"user": {
"type": "String"
}
}
},
"publicInviteDescChat": "ค้นหาผู้ใช้เพื่อนำไปเชิญเข้าร่วมแชทนี้。",
"publicInviteDescSpace": "ค้นหาผู้ใช้เพื่อนำไปเชิญเข้าร่วมพื้นที่นี้。",
"@publicInviteDescChat": {
"type": "String",
"placeholders": {}
},
"@publicInviteDescSpace": {
"type": "String",
"placeholders": {}
}
}

View file

@ -1,6 +1,6 @@
{
"@@locale": "tr",
"@@last_modified": "2026-01-16 14:33:26.649412",
"@@last_modified": "2026-01-22 12:01:49.339268",
"about": "Hakkında",
"@about": {
"type": "String",
@ -11087,5 +11087,64 @@
"@constructUseIncGCDesc": {
"type": "String",
"placeholders": {}
},
"constructUseCorGEDesc": "Doğru dil bilgisi hatası pratiği",
"constructUseIncGEDesc": "Yanlış dil bilgisi hatası pratiği",
"fillInBlank": "Boşluğu doğru seçimle doldurun",
"learn": "Öğren",
"languageUpdated": "Hedef dil güncellendi!",
"@constructUseCorGEDesc": {
"type": "String",
"placeholders": {}
},
"@constructUseIncGEDesc": {
"type": "String",
"placeholders": {}
},
"@fillInBlank": {
"type": "String",
"placeholders": {}
},
"@learn": {
"type": "String",
"placeholders": {}
},
"@languageUpdated": {
"type": "String",
"placeholders": {}
},
"voiceDropdownTitle": "Pangea Bot sesi",
"@voiceDropdownTitle": {
"type": "String",
"placeholders": {}
},
"knockDesc": "Talebiniz kurs yöneticisine gönderildi! Onaylarlarsa içeri alınacaksınız.",
"@knockDesc": {
"type": "String",
"placeholders": {}
},
"joinSpaceOnboardingDesc": "Bir davet kodunuz veya halka açık bir kursa bağlantınız var mı?",
"welcomeUser": "Hoş geldin {user}",
"@joinSpaceOnboardingDesc": {
"type": "String",
"placeholders": {}
},
"@welcomeUser": {
"type": "String",
"placeholders": {
"user": {
"type": "String"
}
}
},
"publicInviteDescChat": "Bu sohbete davet etmek için kullanıcıları arayın.",
"publicInviteDescSpace": "Bu alana davet etmek için kullanıcıları arayın.",
"@publicInviteDescChat": {
"type": "String",
"placeholders": {}
},
"@publicInviteDescSpace": {
"type": "String",
"placeholders": {}
}
}

View file

@ -1,6 +1,6 @@
{
"@@locale": "uk",
"@@last_modified": "2026-01-16 14:32:56.338443",
"@@last_modified": "2026-01-22 12:01:28.947095",
"about": "Про застосунок",
"@about": {
"type": "String",
@ -10859,5 +10859,64 @@
"@constructUseIncGCDesc": {
"type": "String",
"placeholders": {}
},
"constructUseCorGEDesc": "Практика виправлення граматичних помилок",
"constructUseIncGEDesc": "Практика неправильних граматичних помилок",
"fillInBlank": "Заповніть пропуск правильним вибором",
"learn": "Вчити",
"languageUpdated": "Цільова мова оновлена!",
"@constructUseCorGEDesc": {
"type": "String",
"placeholders": {}
},
"@constructUseIncGEDesc": {
"type": "String",
"placeholders": {}
},
"@fillInBlank": {
"type": "String",
"placeholders": {}
},
"@learn": {
"type": "String",
"placeholders": {}
},
"@languageUpdated": {
"type": "String",
"placeholders": {}
},
"voiceDropdownTitle": "Голос Pangea Bot",
"@voiceDropdownTitle": {
"type": "String",
"placeholders": {}
},
"knockDesc": "Ваш запит надіслано адміністратору курсу! Ви будете допущені, якщо вони схвалять.",
"@knockDesc": {
"type": "String",
"placeholders": {}
},
"joinSpaceOnboardingDesc": "У вас є код запрошення або посилання на публічний курс?",
"welcomeUser": "Ласкаво просимо {user}",
"@joinSpaceOnboardingDesc": {
"type": "String",
"placeholders": {}
},
"@welcomeUser": {
"type": "String",
"placeholders": {
"user": {
"type": "String"
}
}
},
"publicInviteDescChat": "Шукайте користувачів, щоб запросити їх до цього чату.",
"publicInviteDescSpace": "Шукайте користувачів, щоб запросити їх до цього простору.",
"@publicInviteDescChat": {
"type": "String",
"placeholders": {}
},
"@publicInviteDescSpace": {
"type": "String",
"placeholders": {}
}
}

View file

@ -1,5 +1,5 @@
{
"@@last_modified": "2026-01-16 14:33:34.535886",
"@@last_modified": "2026-01-22 12:01:54.904291",
"about": "Giới thiệu",
"@about": {
"type": "String",
@ -6435,5 +6435,64 @@
"@constructUseIncGCDesc": {
"type": "String",
"placeholders": {}
},
"constructUseCorGEDesc": "Thực hành lỗi ngữ pháp đúng",
"constructUseIncGEDesc": "Thực hành lỗi ngữ pháp sai",
"fillInBlank": "Điền vào chỗ trống với lựa chọn đúng",
"learn": "Học",
"languageUpdated": "Ngôn ngữ mục tiêu đã được cập nhật!",
"@constructUseCorGEDesc": {
"type": "String",
"placeholders": {}
},
"@constructUseIncGEDesc": {
"type": "String",
"placeholders": {}
},
"@fillInBlank": {
"type": "String",
"placeholders": {}
},
"@learn": {
"type": "String",
"placeholders": {}
},
"@languageUpdated": {
"type": "String",
"placeholders": {}
},
"voiceDropdownTitle": "Giọng nói của Pangea Bot",
"@voiceDropdownTitle": {
"type": "String",
"placeholders": {}
},
"knockDesc": "Yêu cầu của bạn đã được gửi đến quản trị viên khóa học! Bạn sẽ được cho vào nếu họ chấp thuận.",
"@knockDesc": {
"type": "String",
"placeholders": {}
},
"joinSpaceOnboardingDesc": "Bạn có mã mời hoặc liên kết đến một khóa học công khai không?",
"welcomeUser": "Chào mừng {user}",
"@joinSpaceOnboardingDesc": {
"type": "String",
"placeholders": {}
},
"@welcomeUser": {
"type": "String",
"placeholders": {
"user": {
"type": "String"
}
}
},
"publicInviteDescChat": "Tìm kiếm người dùng để mời họ tham gia trò chuyện này.",
"publicInviteDescSpace": "Tìm kiếm người dùng để mời họ tham gia không gian này.",
"@publicInviteDescChat": {
"type": "String",
"placeholders": {}
},
"@publicInviteDescSpace": {
"type": "String",
"placeholders": {}
}
}

View file

@ -1856,7 +1856,7 @@
"selectAll": "全選",
"deselectAll": "取消全選",
"@@locale": "yue",
"@@last_modified": "2026-01-16 14:32:49.483939",
"@@last_modified": "2026-01-22 12:01:25.863585",
"@ignoreUser": {
"type": "String",
"placeholders": {}
@ -11956,5 +11956,64 @@
"@constructUseIncGCDesc": {
"type": "String",
"placeholders": {}
},
"constructUseCorGEDesc": "正確語法錯誤練習",
"constructUseIncGEDesc": "不正確語法錯誤練習",
"fillInBlank": "用正確的選擇填空",
"learn": "學習",
"languageUpdated": "目標語言已更新!",
"@constructUseCorGEDesc": {
"type": "String",
"placeholders": {}
},
"@constructUseIncGEDesc": {
"type": "String",
"placeholders": {}
},
"@fillInBlank": {
"type": "String",
"placeholders": {}
},
"@learn": {
"type": "String",
"placeholders": {}
},
"@languageUpdated": {
"type": "String",
"placeholders": {}
},
"voiceDropdownTitle": "Pangea Bot 聲音",
"@voiceDropdownTitle": {
"type": "String",
"placeholders": {}
},
"knockDesc": "你的請求已經發送給課程管理員!如果他們批准,你將被允許進入。",
"@knockDesc": {
"type": "String",
"placeholders": {}
},
"joinSpaceOnboardingDesc": "你有邀請碼或公共課程的鏈接嗎?",
"welcomeUser": "歡迎 {user}",
"@joinSpaceOnboardingDesc": {
"type": "String",
"placeholders": {}
},
"@welcomeUser": {
"type": "String",
"placeholders": {
"user": {
"type": "String"
}
}
},
"publicInviteDescChat": "搜尋用戶以邀請他們加入此聊天。",
"publicInviteDescSpace": "搜尋用戶以邀請他們加入此空間。",
"@publicInviteDescChat": {
"type": "String",
"placeholders": {}
},
"@publicInviteDescSpace": {
"type": "String",
"placeholders": {}
}
}

View file

@ -1,6 +1,6 @@
{
"@@locale": "zh",
"@@last_modified": "2026-01-16 14:33:45.909821",
"@@last_modified": "2026-01-22 12:02:00.962577",
"about": "关于",
"@about": {
"type": "String",
@ -10856,5 +10856,64 @@
"@constructUseIncGCDesc": {
"type": "String",
"placeholders": {}
},
"constructUseCorGEDesc": "正确语法错误练习",
"constructUseIncGEDesc": "不正确语法错误练习",
"fillInBlank": "用正确的选项填空",
"learn": "学习",
"languageUpdated": "目标语言已更新!",
"@constructUseCorGEDesc": {
"type": "String",
"placeholders": {}
},
"@constructUseIncGEDesc": {
"type": "String",
"placeholders": {}
},
"@fillInBlank": {
"type": "String",
"placeholders": {}
},
"@learn": {
"type": "String",
"placeholders": {}
},
"@languageUpdated": {
"type": "String",
"placeholders": {}
},
"voiceDropdownTitle": "潘吉亚机器人声音",
"@voiceDropdownTitle": {
"type": "String",
"placeholders": {}
},
"knockDesc": "您的请求已发送给课程管理员!如果他们批准,您将被允许进入。",
"@knockDesc": {
"type": "String",
"placeholders": {}
},
"joinSpaceOnboardingDesc": "您是否有邀请代码或公共课程的链接?",
"welcomeUser": "欢迎 {user}",
"@joinSpaceOnboardingDesc": {
"type": "String",
"placeholders": {}
},
"@welcomeUser": {
"type": "String",
"placeholders": {
"user": {
"type": "String"
}
}
},
"publicInviteDescChat": "搜索用户以邀请他们加入此聊天。",
"publicInviteDescSpace": "搜索用户以邀请他们加入此空间。",
"@publicInviteDescChat": {
"type": "String",
"placeholders": {}
},
"@publicInviteDescSpace": {
"type": "String",
"placeholders": {}
}
}

View file

@ -1,5 +1,5 @@
{
"@@last_modified": "2026-01-16 14:33:16.316521",
"@@last_modified": "2026-01-22 12:01:42.417971",
"about": "關於",
"@about": {
"type": "String",
@ -10863,5 +10863,64 @@
"@constructUseIncGCDesc": {
"type": "String",
"placeholders": {}
},
"constructUseCorGEDesc": "正確語法錯誤練習",
"constructUseIncGEDesc": "不正確語法錯誤練習",
"fillInBlank": "用正確的選擇填空",
"learn": "學習",
"languageUpdated": "目標語言已更新!",
"@constructUseCorGEDesc": {
"type": "String",
"placeholders": {}
},
"@constructUseIncGEDesc": {
"type": "String",
"placeholders": {}
},
"@fillInBlank": {
"type": "String",
"placeholders": {}
},
"@learn": {
"type": "String",
"placeholders": {}
},
"@languageUpdated": {
"type": "String",
"placeholders": {}
},
"voiceDropdownTitle": "Pangea Bot 語音",
"@voiceDropdownTitle": {
"type": "String",
"placeholders": {}
},
"knockDesc": "您的請求已發送給課程管理員!如果他們批准,您將被允許進入。",
"@knockDesc": {
"type": "String",
"placeholders": {}
},
"joinSpaceOnboardingDesc": "您是否有邀請碼或公共課程的鏈接?",
"welcomeUser": "歡迎 {user}",
"@joinSpaceOnboardingDesc": {
"type": "String",
"placeholders": {}
},
"@welcomeUser": {
"type": "String",
"placeholders": {
"user": {
"type": "String"
}
}
},
"publicInviteDescChat": "搜尋用戶以邀請他們加入此聊天。",
"publicInviteDescSpace": "搜尋用戶以邀請他們加入此空間。",
"@publicInviteDescChat": {
"type": "String",
"placeholders": {}
},
"@publicInviteDescSpace": {
"type": "String",
"placeholders": {}
}
}

View file

@ -18,7 +18,6 @@ import 'package:fluffychat/pangea/activity_sessions/activity_session_chat/activi
import 'package:fluffychat/pangea/activity_sessions/activity_session_chat/activity_chat_extension.dart';
import 'package:fluffychat/pangea/analytics_data/analytics_update_dispatcher.dart';
import 'package:fluffychat/pangea/analytics_data/analytics_updater_mixin.dart';
import 'package:fluffychat/pangea/analytics_misc/client_analytics_extension.dart';
import 'package:fluffychat/pangea/analytics_misc/construct_type_enum.dart';
import 'package:fluffychat/pangea/analytics_misc/constructs_model.dart';
import 'package:fluffychat/pangea/analytics_misc/level_up/level_up_banner.dart';
@ -498,6 +497,8 @@ class ChatController extends State<ChatPageWithRoom>
if (botAudioEvent == null) return;
final matrix = Matrix.of(context);
if (matrix.voiceMessageEventId.value != null) return;
matrix.voiceMessageEventId.value = botAudioEvent.eventId;
matrix.audioPlayer?.dispose();
matrix.audioPlayer = AudioPlayer();
@ -963,6 +964,9 @@ class ChatController extends State<ChatPageWithRoom>
}
final previousEdit = editEvent;
if (showEmojiPicker) {
hideEmojiPicker();
}
room
.pangeaSendTextEvent(
@ -2022,6 +2026,7 @@ class ChatController extends State<ChatPageWithRoom>
bool showMessageShimmer(Event event) {
if (event.type != EventTypes.Message) return false;
if (!(event.eventId == buttonEventID)) return false;
if (event.messageType == MessageTypes.Text) {
return !InstructionsEnum.clickTextMessages.isToggledOff;
}
@ -2058,31 +2063,6 @@ class ChatController extends State<ChatPageWithRoom>
return;
}
final langCode =
pangeaMessageEvent?.originalSent?.langCode.split('-').first;
if (LanguageMismatchRepo.shouldShowByEvent(event.eventId) &&
langCode != null &&
pangeaMessageEvent?.originalSent?.content.langCodeMatchesL2 == false &&
room.client.allMyAnalyticsRooms.any((r) => r.madeForLang == langCode)) {
LanguageMismatchRepo.setEvent(event.eventId);
OverlayUtil.showLanguageMismatchPopup(
context: context,
targetId: event.eventId,
message: L10n.of(context).messageLanguageMismatchMessage,
targetLanguage: pangeaMessageEvent!.originalSent!.langCode,
onConfirm: () => showToolbar(
event,
pangeaMessageEvent: pangeaMessageEvent,
selectedToken: selectedToken,
mode: mode,
nextEvent: nextEvent,
prevEvent: prevEvent,
),
);
return;
}
final overlayEntry = MessageSelectionOverlay(
chatController: this,
event: event,
@ -2275,7 +2255,7 @@ class ChatController extends State<ChatPageWithRoom>
bool autosend = false,
}) async {
if (shouldShowLanguageMismatchPopupByActivity) {
return showLanguageMismatchPopup();
return showLanguageMismatchPopup(manual: manual);
}
await choreographer.requestWritingAssistance(manual: manual);
@ -2288,7 +2268,7 @@ class ChatController extends State<ChatPageWithRoom>
}
}
void showLanguageMismatchPopup() {
void showLanguageMismatchPopup({bool manual = false}) {
if (!shouldShowLanguageMismatchPopupByActivity) {
return;
}
@ -2301,11 +2281,41 @@ class ChatController extends State<ChatPageWithRoom>
message: L10n.of(context).languageMismatchDesc,
targetLanguage: targetLanguage,
onConfirm: () => WidgetsBinding.instance.addPostFrameCallback(
(_) => onRequestWritingAssistance(manual: false, autosend: true),
(_) => onRequestWritingAssistance(manual: manual, autosend: true),
),
);
}
Future<void> updateLanguageOnMismatch(String target) async {
final messenger = ScaffoldMessenger.of(context);
messenger.hideCurrentSnackBar();
final resp = await showFutureLoadingDialog(
context: context,
future: () async {
clearSelectedEvents();
await MatrixState.pangeaController.userController.updateProfile(
(profile) {
profile.userSettings.targetLanguage = target;
return profile;
},
waitForDataInSync: true,
);
},
);
if (resp.isError) return;
if (mounted) {
messenger.hideCurrentSnackBar();
messenger.showSnackBar(
SnackBar(
content: Text(
L10n.of(context).languageUpdated,
textAlign: TextAlign.center,
),
),
);
}
}
void _onCloseIT() {
if (choreographer.timesDismissedIT.value >= 3) {
showDisableLanguageToolsPopup();
@ -2437,6 +2447,8 @@ class ChatController extends State<ChatPageWithRoom>
);
if (reason == null) return;
clearSelectedEvents();
await showFutureLoadingDialog(
context: context,
future: () => room.sendEvent(

View file

@ -49,8 +49,19 @@ class ChatEmojiPicker extends StatelessWidget {
backgroundColor:
theme.colorScheme.onInverseSurface,
),
bottomActionBarConfig: const BottomActionBarConfig(
enabled: false,
bottomActionBarConfig: BottomActionBarConfig(
// #Pangea
// enabled: false,
showBackspaceButton: false,
backgroundColor: Theme.of(context)
.colorScheme
.surfaceContainer,
buttonColor: Theme.of(context)
.colorScheme
.surfaceContainer,
buttonIconColor:
Theme.of(context).colorScheme.onSurface,
// Pangea#
),
categoryViewConfig: CategoryViewConfig(
backspaceColor: theme.colorScheme.primary,
@ -68,6 +79,17 @@ class ChatEmojiPicker extends StatelessWidget {
)!,
indicatorColor: theme.colorScheme.onSurface,
),
// #Pangea
viewOrderConfig: const ViewOrderConfig(
middle: EmojiPickerItem.searchBar,
top: EmojiPickerItem.categoryBar,
bottom: EmojiPickerItem.emojiView,
),
searchViewConfig: SearchViewConfig(
backgroundColor: theme.colorScheme.surface,
buttonIconColor: theme.colorScheme.onSurface,
),
// Pangea#
),
),
// #Pangea

View file

@ -151,6 +151,7 @@ class AudioPlayerState extends State<AudioPlayerWidget> {
audioPlayer.pause();
audioPlayer.dispose();
matrix.voiceMessageEventId.value = matrix.audioPlayer = null;
matrix.voiceMessageEventId.removeListener(_onPlayerChange);
// #Pangea
_onAudioStateChanged?.cancel();
// Pangea#
@ -173,6 +174,14 @@ class AudioPlayerState extends State<AudioPlayerWidget> {
if (currentPlayer != null) {
// #Pangea
currentPlayer.setSpeed(playbackSpeed);
_onAudioStateChanged?.cancel();
_onAudioStateChanged =
matrix.audioPlayer!.playerStateStream.listen((state) {
if (state.processingState == ProcessingState.completed) {
matrix.audioPlayer!.stop();
matrix.audioPlayer!.seek(Duration.zero);
}
});
// Pangea#
if (currentPlayer.isAtEndPosition) {
currentPlayer.seek(Duration.zero);
@ -382,10 +391,26 @@ class AudioPlayerState extends State<AudioPlayerWidget> {
return eventWaveForm.map((i) => i > 1024 ? 1024 : i).toList();
}
// #Pangea
void _onPlayerChange() {
if (matrix.audioPlayer == null) return;
_onAudioStateChanged?.cancel();
_onAudioStateChanged =
matrix.audioPlayer?.playerStateStream.listen((state) {
if (state.processingState == ProcessingState.completed) {
matrix.audioPlayer?.stop();
matrix.audioPlayer?.seek(Duration.zero);
}
});
}
// Pangea#
@override
void initState() {
super.initState();
matrix = Matrix.of(context);
WidgetsBinding.instance.addPostFrameCallback((_) => _onPlayerChange());
matrix.voiceMessageEventId.addListener(_onPlayerChange);
_waveform = _getWaveform();
// #Pangea

View file

@ -13,10 +13,7 @@ import 'package:fluffychat/pages/chat/events/room_creation_state_event.dart';
import 'package:fluffychat/pangea/activity_sessions/activity_room_extension.dart';
import 'package:fluffychat/pangea/activity_sessions/activity_session_chat/activity_roles_event_widget.dart';
import 'package:fluffychat/pangea/activity_sessions/activity_summary_widget.dart';
import 'package:fluffychat/pangea/bot/utils/bot_name.dart';
import 'package:fluffychat/pangea/bot/widgets/bot_settings_language_icon.dart';
import 'package:fluffychat/pangea/chat/extensions/custom_room_display_extension.dart';
import 'package:fluffychat/pangea/chat/widgets/request_regeneration_button.dart';
import 'package:fluffychat/pangea/common/widgets/pressable_button.dart';
import 'package:fluffychat/pangea/common/widgets/shimmer_background.dart';
import 'package:fluffychat/pangea/events/constants/pangea_event_types.dart';
@ -478,18 +475,6 @@ class Message extends StatelessWidget {
presenceBackgroundColor: wallpaperMode
? Colors.transparent
: null,
// #Pangea
miniIcon:
user.id == BotName.byEnvironment
? BotSettingsLanguageIcon(
user: user,
)
: null,
presenceOffset:
user.id == BotName.byEnvironment
? const Offset(0, 0)
: null,
// Pangea#
);
},
),
@ -822,20 +807,7 @@ class Message extends StatelessWidget {
),
],
),
)
// #Pangea
else if (canRefresh)
RequestRegenerationButton(
textColor:
textColor,
onPressed: () =>
controller
.requestRegeneration(
event
.eventId,
),
),
// Pangea#
],
),
),

View file

@ -111,9 +111,12 @@ class ChatListController extends State<ChatList>
// StreamSubscription? _intentUriStreamSubscription;
// Pangea#
ActiveFilter activeFilter = AppConfig.separateChatTypes
? ActiveFilter.messages
: ActiveFilter.allChats;
// #Pangea
// ActiveFilter activeFilter = AppConfig.separateChatTypes
// ? ActiveFilter.messages
// : ActiveFilter.allChats;
ActiveFilter activeFilter = ActiveFilter.allChats;
// Pangea#
// #Pangea
String? get activeSpaceId => widget.activeSpaceId;

View file

@ -0,0 +1,83 @@
import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart';
import 'package:matrix/matrix.dart';
import 'package:fluffychat/pages/onboarding/space_code_onboarding_view.dart';
import 'package:fluffychat/pangea/common/utils/error_handler.dart';
import 'package:fluffychat/pangea/join_codes/space_code_controller.dart';
import 'package:fluffychat/pangea/spaces/space_constants.dart';
import 'package:fluffychat/widgets/matrix.dart';
class SpaceCodeOnboarding extends StatefulWidget {
const SpaceCodeOnboarding({super.key});
@override
State<SpaceCodeOnboarding> createState() => SpaceCodeOnboardingState();
}
class SpaceCodeOnboardingState extends State<SpaceCodeOnboarding> {
Profile? profile;
Client get client => Matrix.of(context).client;
final TextEditingController codeController = TextEditingController();
@override
void initState() {
_setProfile();
codeController.addListener(() {
if (mounted) setState(() {});
});
super.initState();
}
@override
void dispose() {
codeController.dispose();
super.dispose();
}
Future<void> _setProfile() async {
try {
profile = await client.getProfileFromUserId(
client.userID!,
);
} catch (e, s) {
ErrorHandler.logError(
e: e,
s: s,
data: {
'userId': client.userID,
},
);
} finally {
if (mounted) setState(() {});
}
}
Future<void> submitCode() async {
String code = codeController.text.trim();
if (code.isEmpty) return;
try {
final link = Uri.parse(Uri.parse(code).fragment);
if (link.queryParameters.containsKey(SpaceConstants.classCode)) {
code = link.queryParameters[SpaceConstants.classCode]!;
}
} catch (e) {
debugPrint("Text input is not a URL: $e");
}
final roomId = await SpaceCodeController.joinSpaceWithCode(context, code);
if (roomId != null) {
final room = Matrix.of(context).client.getRoomById(roomId);
room?.isSpace ?? true
? context.go('/rooms/spaces/$roomId/details')
: context.go('/rooms/$roomId');
}
}
@override
Widget build(BuildContext context) =>
SpaceCodeOnboardingView(controller: this);
}

View file

@ -0,0 +1,82 @@
import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart';
import 'package:matrix/matrix.dart';
import 'package:fluffychat/l10n/l10n.dart';
import 'package:fluffychat/pages/onboarding/space_code_onboarding.dart';
import 'package:fluffychat/pangea/authentication/p_logout.dart';
import 'package:fluffychat/pangea/login/pages/pangea_login_scaffold.dart';
class SpaceCodeOnboardingView extends StatelessWidget {
final SpaceCodeOnboardingState controller;
const SpaceCodeOnboardingView({
super.key,
required this.controller,
});
@override
Widget build(BuildContext context) {
return PangeaLoginScaffold(
customAppBar: AppBar(
leading: BackButton(
onPressed: () => pLogoutAction(
context,
bypassWarning: true,
),
),
),
showAppName: false,
mainAssetUrl: controller.profile?.avatarUrl,
children: [
Column(
spacing: 8.0,
children: [
Text(
L10n.of(context).welcomeUser(
controller.profile?.displayName ??
controller.client.userID?.localpart ??
"",
),
style: Theme.of(context)
.textTheme
.titleLarge
?.copyWith(fontWeight: FontWeight.bold),
),
Text(
L10n.of(context).joinSpaceOnboardingDesc,
textAlign: TextAlign.center,
),
TextField(
decoration: InputDecoration(
hintText: L10n.of(context).enterCodeToJoin,
),
controller: controller.codeController,
onSubmitted: (_) => controller.submitCode,
),
ElevatedButton(
onPressed: controller.codeController.text.isNotEmpty
? controller.submitCode
: null,
style: ElevatedButton.styleFrom(
backgroundColor: Theme.of(context).colorScheme.primaryContainer,
foregroundColor:
Theme.of(context).colorScheme.onPrimaryContainer,
),
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(L10n.of(context).join),
],
),
),
TextButton(
child: Text(L10n.of(context).skipForNow),
onPressed: () => context.go("/rooms"),
),
],
),
],
);
}
}

View file

@ -147,9 +147,7 @@ class SettingsView extends StatelessWidget {
displayname,
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: const TextStyle(
fontSize: 18,
),
style: const TextStyle(fontSize: 18),
),
),
TextButton.icon(
@ -171,25 +169,6 @@ class SettingsView extends StatelessWidget {
// style: const TextStyle(fontSize: 12),
),
),
// #Pangea
TextButton.icon(
onPressed: controller.setStatus,
icon: const Icon(
Icons.add,
size: 14,
),
style: TextButton.styleFrom(
foregroundColor:
theme.colorScheme.secondary,
iconColor: theme.colorScheme.secondary,
),
label: Text(
L10n.of(context).setStatus,
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
),
// Pangea#
],
),
),

View file

@ -1,7 +1,7 @@
import 'package:flutter/material.dart';
import 'package:fluffychat/config/app_config.dart';
import 'package:fluffychat/config/setting_keys.dart';
import 'package:fluffychat/pangea/user/style_settings_repo.dart';
import 'package:fluffychat/utils/account_config.dart';
import 'package:fluffychat/utils/file_selector.dart';
import 'package:fluffychat/widgets/future_loading_dialog.dart';
@ -157,10 +157,16 @@ class SettingsStyleController extends State<SettingsStyle> {
void changeFontSizeFactor(double d) {
setState(() => AppConfig.fontSizeFactor = d);
Matrix.of(context).store.setString(
SettingKeys.fontSizeFactor,
AppConfig.fontSizeFactor.toString(),
);
// #Pangea
// Matrix.of(context).store.setString(
// SettingKeys.fontSizeFactor,
// AppConfig.fontSizeFactor.toString(),
// );
StyleSettingsRepo.setFontSizeFactor(
Matrix.of(context).client.userID!,
AppConfig.fontSizeFactor,
);
// Pangea#
}
@override

View file

@ -353,14 +353,14 @@ class SettingsStyleView extends StatelessWidget {
storeKey: SettingKeys.showPresences,
defaultValue: AppConfig.showPresences,
),
SettingsSwitchListTile.adaptive(
title: L10n.of(context).separateChatTypes,
onChanged: (b) => AppConfig.separateChatTypes = b,
storeKey: SettingKeys.separateChatTypes,
defaultValue: AppConfig.separateChatTypes,
),
// #Pangea
// SettingsSwitchListTile.adaptive(
// title: L10n.of(context).separateChatTypes,
// onChanged: (b) => AppConfig.separateChatTypes = b,
// storeKey: SettingKeys.separateChatTypes,
// defaultValue: AppConfig.separateChatTypes,
// ),
// SettingsSwitchListTile.adaptive(
// title: L10n.of(context).displayNavigationRail,
// onChanged: (b) => AppConfig.displayNavigationRail = b,
// storeKey: SettingKeys.displayNavigationRail,

View file

@ -6,8 +6,6 @@ import 'package:shimmer/shimmer.dart';
import 'package:fluffychat/config/app_config.dart';
import 'package:fluffychat/l10n/l10n.dart';
import 'package:fluffychat/pangea/bot/utils/bot_name.dart';
import 'package:fluffychat/pangea/bot/widgets/bot_settings_language_icon.dart';
import 'package:fluffychat/utils/string_color.dart';
import 'package:fluffychat/widgets/avatar.dart';
import 'package:fluffychat/widgets/hover_builder.dart';
@ -70,14 +68,6 @@ class ActivityParticipantIndicator extends StatelessWidget {
name: userId!.localpart,
size: 60.0,
userId: userId,
miniIcon:
room != null && userId == BotName.byEnvironment
? BotSettingsLanguageIcon(user: user!)
: null,
presenceOffset:
room != null && userId == BotName.byEnvironment
? const Offset(0, 0)
: null,
)
: ClipRRect(
borderRadius: BorderRadius.circular(30),

View file

@ -44,15 +44,9 @@ extension ActivityMenuLogic on ChatController {
return false;
}
final l1 =
MatrixState.pangeaController.userController.userL1?.langCodeShort;
final l2 =
MatrixState.pangeaController.userController.userL2?.langCodeShort;
final activityLang = room.activityPlan?.req.targetLanguage.split('-').first;
return activityLang != null &&
l2 != null &&
l2 != activityLang &&
l1 != activityLang;
return activityLang != null && l2 != activityLang;
}
}

View file

@ -106,27 +106,17 @@ class _VocabChipsState extends State<_VocabChips> with TokenRenderingMixin {
OverlayUtil.showPositionedCard(
overlayKey: target,
context: context,
cardToShow: StatefulBuilder(
builder: (context, setState) => WordZoomWidget(
token: PangeaTokenText(
content: v.lemma,
length: v.lemma.characters.length,
offset: 0,
),
construct: ConstructIdentifier(
lemma: v.lemma,
type: ConstructTypeEnum.vocab,
category: v.pos,
),
langCode: widget.langCode,
onClose: () {
MatrixState.pAnyState.closeOverlay(target);
setState(() => _selectedVocab = null);
},
onDismissNewWordOverlay: () {
if (mounted) setState(() {});
},
),
cardToShow: _WordCardWrapper(
v: v,
langCode: widget.langCode,
target: target,
onClose: () {
if (mounted) {
WidgetsBinding.instance.addPostFrameCallback(
(_) => setState(() => _selectedVocab = null),
);
}
},
),
transformTargetId: target,
closePrevOverlay: false,
@ -204,3 +194,52 @@ class _VocabChipsState extends State<_VocabChips> with TokenRenderingMixin {
);
}
}
class _WordCardWrapper extends StatefulWidget {
final Vocab v;
final String langCode;
final String target;
final VoidCallback onClose;
const _WordCardWrapper({
required this.v,
required this.langCode,
required this.target,
required this.onClose,
});
@override
State<_WordCardWrapper> createState() => _WordCardWrapperState();
}
class _WordCardWrapperState extends State<_WordCardWrapper> {
@override
void dispose() {
widget.onClose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return WordZoomWidget(
token: PangeaTokenText(
content: widget.v.lemma,
length: widget.v.lemma.characters.length,
offset: 0,
),
construct: ConstructIdentifier(
lemma: widget.v.lemma,
type: ConstructTypeEnum.vocab,
category: widget.v.pos,
),
langCode: widget.langCode,
onClose: () {
MatrixState.pAnyState.closeOverlay(widget.target);
widget.onClose();
},
onDismissNewWordOverlay: () {
if (mounted) setState(() {});
},
);
}
}

View file

@ -97,7 +97,6 @@ class SpaceAnalyticsSummaryModel {
Set<ConstructIdentifier> blockedConstructs,
int numCompletedActivities,
) {
int totalXP = 0;
int numWordsTyped = 0;
int numChoicesCorrect = 0;
int numChoicesIncorrect = 0;
@ -114,7 +113,9 @@ class SpaceAnalyticsSummaryModel {
mergeTable.addConstructsByUses(e.content.uses, blockedConstructs);
for (final use in e.content.uses) {
totalXP += use.xp;
final id = use.identifier;
if (blockedConstructs.contains(id)) continue;
allUses.add(use);
if (use.useType.summaryEnumType ==
@ -132,8 +133,7 @@ class SpaceAnalyticsSummaryModel {
sentEventIds.add(use.metadata.eventId!);
}
final id = use.identifier;
final existing = id.type == ConstructTypeEnum.vocab
final existing = use.identifier.type == ConstructTypeEnum.vocab
? aggregatedVocab[id]
: aggregatedMorph[id];
@ -189,6 +189,10 @@ class SpaceAnalyticsSummaryModel {
}
}
final totalXP = cleanedVocab.values
.fold<int>(0, (sum, entry) => sum + entry.points) +
cleanedMorph.values.fold<int>(0, (sum, entry) => sum + entry.points);
final level = DerivedAnalyticsDataModel.calculateLevelWithXp(totalXP);
final uniqueVocabCount = cleanedVocab.length;
final uniqueMorphCount = cleanedMorph.length;

View file

@ -6,6 +6,7 @@ import 'package:matrix/matrix.dart';
import 'package:fluffychat/pangea/analytics_misc/client_analytics_extension.dart';
import 'package:fluffychat/pangea/analytics_misc/construct_use_model.dart';
import 'package:fluffychat/pangea/events/event_wrappers/pangea_message_event.dart';
import 'package:fluffychat/pangea/events/models/pangea_token_model.dart';
class ExampleMessageUtil {
static Future<List<InlineSpan>?> getExampleMessage(
@ -49,14 +50,27 @@ class ExampleMessageUtil {
String? form,
PangeaMessageEvent messageEvent,
) {
final tokens = messageEvent.messageDisplayRepresentation?.tokens;
if (tokens == null || tokens.isEmpty) return null;
final token = tokens.firstWhereOrNull(
(token) => token.text.content == form,
);
if (token == null) return null;
PangeaToken? token;
String? text;
final text = messageEvent.messageDisplayText;
if (messageEvent.isAudioMessage) {
final stt = messageEvent.getSpeechToTextLocal();
if (stt == null) return null;
final tokens = stt.transcript.sttTokens.map((t) => t.token).toList();
token = tokens.firstWhereOrNull(
(token) => token.text.content == form,
);
text = stt.transcript.text;
} else {
final tokens = messageEvent.messageDisplayRepresentation?.tokens;
if (tokens == null || tokens.isEmpty) return null;
token = tokens.firstWhereOrNull(
(token) => token.text.content == form,
);
text = messageEvent.messageDisplayText;
}
if (token == null) return null;
final tokenText = token.text.content;
int tokenIndex = text.indexOf(tokenText);
if (tokenIndex == -1) return null;

View file

@ -58,7 +58,7 @@ class SessionLoader extends AsyncLoader<AnalyticsPracticeSessionModel> {
}
class AnalyticsPractice extends StatefulWidget {
static bool bypassExitConfirmation = false;
static bool bypassExitConfirmation = true;
final ConstructTypeEnum type;
const AnalyticsPractice({
@ -83,6 +83,7 @@ class AnalyticsPracticeState extends State<AnalyticsPractice>
ValueNotifier<MessageActivityRequest?>(null);
final ValueNotifier<double> progressNotifier = ValueNotifier<double>(0.0);
final ValueNotifier<bool> enableChoicesNotifier = ValueNotifier<bool>(true);
final Map<String, Map<String, String>> _choiceTexts = {};
final Map<String, Map<String, String?>> _choiceEmojis = {};
@ -106,6 +107,7 @@ class AnalyticsPracticeState extends State<AnalyticsPractice>
activityState.dispose();
activityTarget.dispose();
progressNotifier.dispose();
enableChoicesNotifier.dispose();
super.dispose();
}
@ -187,17 +189,18 @@ class AnalyticsPracticeState extends State<AnalyticsPractice>
String choiceTargetId(String choiceId) =>
'${widget.type.name}-choice-card-${choiceId.replaceAll(' ', '_')}';
void _resetActivityState() {
void _clearState() {
activityState.value = const AsyncState.loading();
activityTarget.value = null;
}
enableChoicesNotifier.value = true;
void _resetSessionState() {
progressNotifier.value = 0.0;
_queue.clear();
_choiceTexts.clear();
_choiceEmojis.clear();
activityState.value = const AsyncState.idle();
AnalyticsPractice.bypassExitConfirmation = true;
}
void updateElapsedTime(int seconds) {
@ -224,8 +227,7 @@ class AnalyticsPracticeState extends State<AnalyticsPractice>
Future<void> _onLanguageUpdate() async {
try {
_resetActivityState();
_resetSessionState();
_clearState();
await _analyticsService
.updateDispatcher.constructUpdateStream.stream.first
.timeout(const Duration(seconds: 10));
@ -242,14 +244,14 @@ class AnalyticsPracticeState extends State<AnalyticsPractice>
Future<void> _startSession() async {
await _waitForAnalytics();
await _sessionLoader.load();
if (_sessionLoader.isError) return;
progressNotifier.value = _sessionLoader.value!.progress;
await _continueSession();
}
Future<void> reloadSession() async {
_resetActivityState();
_resetSessionState();
_clearState();
_sessionLoader.reset();
await _startSession();
}
@ -272,6 +274,7 @@ class AnalyticsPracticeState extends State<AnalyticsPractice>
Future<void> _continueSession() async {
if (_continuing) return;
_continuing = true;
enableChoicesNotifier.value = true;
try {
if (activityState.value
@ -288,8 +291,10 @@ class AnalyticsPracticeState extends State<AnalyticsPractice>
final activity = await nextActivityCompleter.completer.future;
activityState.value = AsyncState.loaded(activity);
AnalyticsPractice.bypassExitConfirmation = false;
}
} catch (e) {
AnalyticsPractice.bypassExitConfirmation = true;
activityState.value = AsyncState.error(e);
} finally {
_continuing = false;
@ -313,7 +318,9 @@ class AnalyticsPracticeState extends State<AnalyticsPractice>
if (!mounted) return;
activityState.value = AsyncState.loaded(res);
AnalyticsPractice.bypassExitConfirmation = false;
} catch (e) {
AnalyticsPractice.bypassExitConfirmation = true;
if (!mounted) return;
activityState.value = AsyncState.error(e);
return;
@ -403,6 +410,10 @@ class AnalyticsPracticeState extends State<AnalyticsPractice>
) async {
if (_currentActivity == null) return;
final activity = _currentActivity!;
final isCorrect = activity.multipleChoiceContent.isCorrect(choiceContent);
if (isCorrect) {
enableChoicesNotifier.value = false;
}
// Update activity record
PracticeRecordController.onSelectChoice(
@ -434,12 +445,7 @@ class AnalyticsPracticeState extends State<AnalyticsPractice>
PracticeTarget target,
) async {
final token = target.tokens.first;
final construct = switch (widget.type) {
ConstructTypeEnum.vocab => token.vocabConstructID,
ConstructTypeEnum.morph => token.morphIdByFeature(target.morphFeature!),
};
if (construct == null) return null;
final construct = target.targetTokenConstructID(token);
String? form;
if (widget.type == ConstructTypeEnum.morph) {

View file

@ -4,6 +4,7 @@ import 'package:fluffychat/pangea/analytics_misc/construct_type_enum.dart';
import 'package:fluffychat/pangea/analytics_misc/construct_use_type_enum.dart';
import 'package:fluffychat/pangea/analytics_practice/analytics_practice_constants.dart';
import 'package:fluffychat/pangea/analytics_practice/analytics_practice_session_model.dart';
import 'package:fluffychat/pangea/common/network/requests.dart';
import 'package:fluffychat/pangea/constructs/construct_identifier.dart';
import 'package:fluffychat/pangea/events/event_wrappers/pangea_message_event.dart';
import 'package:fluffychat/pangea/events/models/pangea_token_model.dart';
@ -15,10 +16,17 @@ import 'package:fluffychat/pangea/practice_activities/message_activity_request.d
import 'package:fluffychat/pangea/practice_activities/practice_target.dart';
import 'package:fluffychat/widgets/matrix.dart';
class InsufficientDataException implements Exception {}
class AnalyticsPracticeSessionRepo {
static Future<AnalyticsPracticeSessionModel> get(
ConstructTypeEnum type,
) async {
if (MatrixState.pangeaController.subscriptionController.isSubscribed ==
false) {
throw UnsubscribedException();
}
final r = Random();
final activityTypes = ActivityTypeEnum.analyticsPracticeTypes(type);
@ -67,6 +75,10 @@ class AnalyticsPracticeSessionRepo {
}
}
if (targets.isEmpty) {
throw InsufficientDataException();
}
final session = AnalyticsPracticeSessionModel(
userL1: MatrixState.pangeaController.userController.userL1!.langCode,
userL2: MatrixState.pangeaController.userController.userL2!.langCode,

View file

@ -19,6 +19,7 @@ import 'package:fluffychat/pangea/instructions/instructions_inline_tooltip.dart'
import 'package:fluffychat/pangea/phonetic_transcription/phonetic_transcription_widget.dart';
import 'package:fluffychat/pangea/practice_activities/activity_type_enum.dart';
import 'package:fluffychat/pangea/practice_activities/practice_activity_model.dart';
import 'package:fluffychat/utils/localized_exception_extension.dart';
import 'package:fluffychat/widgets/layouts/max_width_body.dart';
import 'package:fluffychat/widgets/matrix.dart';
@ -84,7 +85,9 @@ class AnalyticsPracticeView extends StatelessWidget {
builder: (context, state, __) {
return switch (state) {
AsyncError<AnalyticsPracticeSessionModel>(:final error) =>
ErrorIndicator(message: error.toString()),
ErrorIndicator(
message: error.toLocalizedString(context),
),
AsyncLoaded<AnalyticsPracticeSessionModel>(:final value) =>
value.isComplete
? CompletedActivitySessionView(state.value, controller)
@ -346,25 +349,29 @@ class _ActivityChoicesWidget extends StatelessWidget {
return Container(
constraints: const BoxConstraints(maxHeight: 400.0),
child: Column(
spacing: 4.0,
mainAxisSize: MainAxisSize.min,
children: choices
.map(
(choice) => _ChoiceCard(
activity: value,
targetId:
controller.choiceTargetId(choice.choiceId),
choiceId: choice.choiceId,
onPressed: () => controller.onSelectChoice(
choice.choiceId,
child: ValueListenableBuilder(
valueListenable: controller.enableChoicesNotifier,
builder: (context, enabled, __) => Column(
spacing: 4.0,
mainAxisSize: MainAxisSize.min,
children: choices
.map(
(choice) => _ChoiceCard(
activity: value,
targetId:
controller.choiceTargetId(choice.choiceId),
choiceId: choice.choiceId,
onPressed: () => controller.onSelectChoice(
choice.choiceId,
),
cardHeight: cardHeight,
choiceText: choice.choiceText,
choiceEmoji: choice.choiceEmoji,
enabled: enabled,
),
cardHeight: cardHeight,
choiceText: choice.choiceText,
choiceEmoji: choice.choiceEmoji,
),
)
.toList(),
)
.toList(),
),
),
);
},
@ -390,6 +397,7 @@ class _ChoiceCard extends StatelessWidget {
final String choiceText;
final String? choiceEmoji;
final bool enabled;
const _ChoiceCard({
required this.activity,
@ -399,6 +407,7 @@ class _ChoiceCard extends StatelessWidget {
required this.cardHeight,
required this.choiceText,
required this.choiceEmoji,
this.enabled = true,
});
@override
@ -420,6 +429,7 @@ class _ChoiceCard extends StatelessWidget {
onPressed: onPressed,
isCorrect: isCorrect,
height: cardHeight,
isEnabled: enabled,
);
case ActivityTypeEnum.lemmaAudio:
@ -432,6 +442,7 @@ class _ChoiceCard extends StatelessWidget {
onPressed: onPressed,
isCorrect: isCorrect,
height: cardHeight,
isEnabled: enabled,
);
case ActivityTypeEnum.grammarCategory:
@ -445,6 +456,7 @@ class _ChoiceCard extends StatelessWidget {
tag: choiceText,
onPressed: onPressed,
isCorrect: isCorrect,
enabled: enabled,
);
case ActivityTypeEnum.grammarError:
@ -458,6 +470,7 @@ class _ChoiceCard extends StatelessWidget {
onPressed: onPressed,
isCorrect: isCorrect,
height: cardHeight,
isEnabled: enabled,
child: Text(choiceText),
);
@ -471,6 +484,7 @@ class _ChoiceCard extends StatelessWidget {
onPressed: onPressed,
isCorrect: isCorrect,
height: cardHeight,
isEnabled: enabled,
child: Text(choiceText),
);
}

View file

@ -62,6 +62,7 @@ class _GameChoiceCardState extends State<GameChoiceCard>
Future<void> _handleTap() async {
if (!widget.isEnabled) return;
widget.onPressed();
if (widget.shouldFlip) {
if (_controller.isAnimating || _revealed) return;
@ -73,8 +74,6 @@ class _GameChoiceCardState extends State<GameChoiceCard>
if (_clicked) return;
setState(() => _clicked = true);
}
widget.onPressed();
}
@override

View file

@ -15,6 +15,7 @@ class GrammarChoiceCard extends StatelessWidget {
final VoidCallback onPressed;
final bool isCorrect;
final double height;
final bool enabled;
const GrammarChoiceCard({
required this.choiceId,
@ -24,6 +25,7 @@ class GrammarChoiceCard extends StatelessWidget {
required this.onPressed,
required this.isCorrect,
this.height = 72.0,
this.enabled = true,
super.key,
});
@ -42,6 +44,7 @@ class GrammarChoiceCard extends StatelessWidget {
onPressed: onPressed,
isCorrect: isCorrect,
height: height,
isEnabled: enabled,
child: Text(copy),
);
}

View file

@ -147,6 +147,7 @@ class LearningProgressIndicators extends StatelessWidget {
.colorScheme
.primary,
),
textScaler: TextScaler.noScaling,
),
if (userL1 != null && userL2 != null)
const Icon(Icons.chevron_right_outlined),
@ -162,6 +163,7 @@ class LearningProgressIndicators extends StatelessWidget {
.colorScheme
.primary,
),
textScaler: TextScaler.noScaling,
),
],
),

View file

@ -125,6 +125,7 @@ class AnimatedFloatingNumberState extends State<AnimatedFloatingNumber>
Text(
widget.number.toString(),
style: indicatorStyle,
textScaler: TextScaler.noScaling,
),
],
);

View file

@ -1,194 +0,0 @@
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:dropdown_button2/dropdown_button2.dart';
import 'package:matrix/matrix.dart';
import 'package:fluffychat/l10n/l10n.dart';
import 'package:fluffychat/pangea/activity_sessions/activity_room_extension.dart';
import 'package:fluffychat/pangea/bot/utils/bot_room_extension.dart';
import 'package:fluffychat/pangea/chat_settings/models/bot_options_model.dart';
import 'package:fluffychat/pangea/chat_settings/widgets/language_level_dropdown.dart';
import 'package:fluffychat/pangea/common/utils/error_handler.dart';
import 'package:fluffychat/pangea/common/widgets/dropdown_text_button.dart';
import 'package:fluffychat/pangea/languages/language_model.dart';
import 'package:fluffychat/pangea/languages/p_language_store.dart';
import 'package:fluffychat/pangea/learning_settings/language_level_type_enum.dart';
import 'package:fluffychat/pangea/learning_settings/p_language_dropdown.dart';
import 'package:fluffychat/widgets/matrix.dart';
class BotChatSettingsDialog extends StatefulWidget {
final Room room;
const BotChatSettingsDialog({
required this.room,
super.key,
});
@override
BotChatSettingsDialogState createState() => BotChatSettingsDialogState();
}
class BotChatSettingsDialogState extends State<BotChatSettingsDialog> {
LanguageModel? _selectedLang;
LanguageLevelTypeEnum? _selectedLevel;
String? _selectedVoice;
@override
void initState() {
final botSettings = widget.room.botOptions;
final activityPlan = _isActivity ? widget.room.activityPlan : null;
_selectedLevel = activityPlan?.req.cefrLevel ?? botSettings?.languageLevel;
_selectedVoice = botSettings?.targetVoice;
final lang =
activityPlan?.req.targetLanguage ?? botSettings?.targetLanguage;
if (lang != null) {
_selectedLang = PLanguageStore.byLangCode(lang);
}
super.initState();
}
bool get _isActivity => widget.room.isActivitySession;
Future<void> _setLanguage(LanguageModel? lang) async {
setState(() {
_selectedLang = lang;
_selectedVoice = null;
});
final model = widget.room.botOptions ?? BotOptionsModel();
model.targetLanguage = lang?.langCode;
model.targetVoice = null;
try {
await widget.room.setBotOptions(model);
} catch (e, s) {
ErrorHandler.logError(
e: e,
s: s,
data: {
'roomId': widget.room.id,
'langCode': lang?.langCode,
},
);
}
}
Future<void> _setLevel(LanguageLevelTypeEnum? level) async {
if (level == null) return;
setState(() => _selectedLevel = level);
final model = widget.room.botOptions ?? BotOptionsModel();
model.languageLevel = level;
try {
await widget.room.setBotOptions(model);
} catch (e, s) {
ErrorHandler.logError(
e: e,
s: s,
data: {
'roomId': widget.room.id,
'level': level.name,
},
);
}
}
Future<void> _setVoice(String? voice) async {
setState(() => _selectedVoice = voice);
final model = widget.room.botOptions ?? BotOptionsModel();
model.targetVoice = voice;
try {
await widget.room.setBotOptions(model);
} catch (e, s) {
ErrorHandler.logError(
e: e,
s: s,
data: {
'roomId': widget.room.id,
'voice': voice,
},
);
}
}
@override
Widget build(BuildContext context) {
return Material(
type: MaterialType.transparency,
child: Column(
spacing: 12.0,
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
if (widget.room.isActivitySession)
ListTile(
contentPadding: const EdgeInsets.all(0.0),
minLeadingWidth: 12.0,
leading: Icon(
Icons.info_outline,
size: 12.0,
color: Theme.of(context).disabledColor,
),
title: Text(
L10n.of(context).activitySettingsOverrideWarning,
style: TextStyle(
color: Theme.of(context).disabledColor,
fontSize: 12.0,
),
),
)
else
const SizedBox(),
PLanguageDropdown(
onChange: _setLanguage,
initialLanguage: _selectedLang,
languages:
MatrixState.pangeaController.pLanguageStore.targetOptions,
isL2List: true,
decorationText: L10n.of(context).targetLanguage,
enabled: !widget.room.isActivitySession,
),
LanguageLevelDropdown(
initialLevel: _selectedLevel,
onChanged: _setLevel,
enabled: !widget.room.isActivitySession,
width: 300,
),
DropdownButtonFormField2<String>(
customButton: _selectedVoice != null
? CustomDropdownTextButton(text: _selectedVoice!)
: null,
menuItemStyleData: const MenuItemStyleData(
padding: EdgeInsets.symmetric(
vertical: 8.0,
horizontal: 16.0,
),
),
decoration: InputDecoration(
labelText: L10n.of(context).voice,
),
isExpanded: true,
dropdownStyleData: DropdownStyleData(
maxHeight: kIsWeb ? 250 : null,
decoration: BoxDecoration(
color: Theme.of(context).colorScheme.surfaceContainerHigh,
borderRadius: BorderRadius.circular(14.0),
),
),
items: (_selectedLang?.voices ?? <String>[]).map((voice) {
return DropdownMenuItem(
value: voice,
child: Text(voice),
);
}).toList(),
onChanged: _setVoice,
value: _selectedVoice,
),
const SizedBox(),
],
),
);
}
}

View file

@ -1,57 +0,0 @@
import 'package:flutter/material.dart';
import 'package:matrix/matrix.dart';
import 'package:fluffychat/pangea/activity_sessions/activity_room_extension.dart';
import 'package:fluffychat/pangea/bot/utils/bot_room_extension.dart';
import 'package:fluffychat/pangea/extensions/pangea_room_extension.dart';
import 'package:fluffychat/widgets/member_actions_popup_menu_button.dart';
class BotSettingsLanguageIcon extends StatelessWidget {
final User user;
const BotSettingsLanguageIcon({
super.key,
required this.user,
});
@override
Widget build(BuildContext context) {
final room = user.room;
String? langCode = room.botOptions?.targetLanguage;
if (room.isActivitySession && room.activityPlan != null) {
langCode = room.activityPlan!.req.targetLanguage;
}
if (langCode == null) {
return const SizedBox();
}
langCode = langCode.split('-').first;
return InkWell(
borderRadius: BorderRadius.circular(32.0),
onTap: room.isRoomAdmin
? () => showMemberActionsPopupMenu(
context: context,
user: user,
room: room,
)
: null,
child: Container(
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(16.0),
color: Theme.of(context).colorScheme.primaryContainer,
),
padding: const EdgeInsets.symmetric(horizontal: 4.0),
child: Text(
langCode,
style: TextStyle(
fontSize: 10.0,
color: Theme.of(context).colorScheme.onPrimaryContainer,
fontWeight: FontWeight.bold,
),
textAlign: TextAlign.center,
),
),
);
}
}

View file

@ -31,18 +31,30 @@ class ChatInputBar extends StatelessWidget {
valueListenable: controller.choreographer.itController.open,
builder: (context, open, __) {
return open
? InstructionsInlineTooltip(
instructionsEnum: InstructionsEnum.clickBestOption,
animate: false,
padding: EdgeInsets.only(
left: 16.0,
right: 16.0,
top: FluffyThemes.isColumnMode(context) ? 16.0 : 8.0,
? Container(
constraints: const BoxConstraints(
maxWidth: FluffyThemes.maxTimelineWidth,
),
alignment: Alignment.center,
child: InstructionsInlineTooltip(
instructionsEnum: InstructionsEnum.clickBestOption,
animate: false,
padding: EdgeInsets.only(
left: 16.0,
right: 16.0,
top: FluffyThemes.isColumnMode(context) ? 16.0 : 8.0,
),
),
)
: ActivityRoleTooltip(
room: controller.room,
hide: controller.choreographer.itController.open,
: Container(
constraints: const BoxConstraints(
maxWidth: FluffyThemes.maxTimelineWidth,
),
alignment: Alignment.center,
child: ActivityRoleTooltip(
room: controller.room,
hide: controller.choreographer.itController.open,
),
);
},
),

View file

@ -1,59 +0,0 @@
import 'package:flutter/material.dart';
import 'package:fluffychat/l10n/l10n.dart';
class RequestRegenerationButton extends StatelessWidget {
final Color textColor;
final VoidCallback onPressed;
const RequestRegenerationButton({
super.key,
required this.textColor,
required this.onPressed,
});
@override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.only(
bottom: 8.0,
left: 16.0,
right: 16.0,
),
child: TextButton(
style: TextButton.styleFrom(
padding: EdgeInsets.zero,
minimumSize: const Size(
0,
0,
),
),
onPressed: onPressed,
child: Row(
mainAxisSize: MainAxisSize.min,
spacing: 4.0,
children: [
Icon(
Icons.refresh,
color: textColor.withAlpha(
164,
),
size: 14,
),
Text(
L10n.of(
context,
).requestRegeneration,
style: TextStyle(
color: textColor.withAlpha(
164,
),
fontSize: 11,
),
),
],
),
),
);
}
}

View file

@ -8,7 +8,6 @@ import 'package:matrix/matrix.dart';
import 'package:fluffychat/l10n/l10n.dart';
import 'package:fluffychat/pangea/extensions/pangea_rooms_chunk_extension.dart';
import 'package:fluffychat/pangea/join_codes/space_code_controller.dart';
import 'package:fluffychat/pangea/navigation/navigation_util.dart';
import 'package:fluffychat/utils/adaptive_bottom_sheet.dart';
import 'package:fluffychat/widgets/avatar.dart';
import 'package:fluffychat/widgets/future_loading_dialog.dart';
@ -30,7 +29,7 @@ class PublicRoomBottomSheet extends StatefulWidget {
assert(roomAlias != null || chunk != null);
}
static Future<bool?> show({
static Future<String?> show({
required BuildContext context,
String? roomAlias,
PublicRoomsChunk? chunk,
@ -91,26 +90,13 @@ class PublicRoomBottomSheetState extends State<PublicRoomBottomSheet> {
notFoundError: L10n.of(context).notTheCodeError,
);
if (resp != null) {
Navigator.of(context).pop(true);
}
}
void _goToRoom(String roomID) {
if (chunk?.roomType != 'm.space' && !client.getRoomById(roomID)!.isSpace) {
NavigationUtil.goToSpaceRoute(
roomID,
[],
context,
);
} else {
context.go('/rooms/spaces/$roomID/details');
Navigator.of(context).pop(resp);
}
}
Future<void> _joinRoom() async {
if (_isRoomMember) {
_goToRoom(room!.id);
Navigator.of(context).pop();
Navigator.of(context).pop(room!.id);
return;
}
@ -131,15 +117,13 @@ class PublicRoomBottomSheetState extends State<PublicRoomBottomSheet> {
);
if (result.result != null) {
_goToRoom(result.result!);
Navigator.of(context).pop(true);
Navigator.of(context).pop(result.result!);
}
}
Future<void> _knockRoom() async {
if (_isRoomMember) {
_goToRoom(room!.id);
Navigator.of(context).pop();
Navigator.of(context).pop(room!.id);
return;
}

View file

@ -8,23 +8,23 @@ import 'package:fluffychat/pangea/common/utils/error_handler.dart';
import 'package:fluffychat/pangea/learning_settings/language_level_type_enum.dart';
class BotOptionsModel {
LanguageLevelTypeEnum languageLevel;
String topic;
List<String> keywords;
bool safetyModeration;
String mode;
String? discussionTopic;
String? discussionKeywords;
bool? discussionTriggerReactionEnabled;
String? discussionTriggerReactionKey;
String? customSystemPrompt;
bool? customTriggerReactionEnabled;
String? customTriggerReactionKey;
String? textAdventureGameMasterInstructions;
String? targetLanguage;
String? targetVoice;
final LanguageLevelTypeEnum languageLevel;
final String topic;
final List<String> keywords;
final bool safetyModeration;
final String mode;
final String? discussionTopic;
final String? discussionKeywords;
final bool? discussionTriggerReactionEnabled;
final String? discussionTriggerReactionKey;
final String? customSystemPrompt;
final bool? customTriggerReactionEnabled;
final String? customTriggerReactionKey;
final String? textAdventureGameMasterInstructions;
final String? targetLanguage;
final String? targetVoice;
BotOptionsModel({
const BotOptionsModel({
////////////////////////////////////////////////////////////////////////////
// General Bot Options
////////////////////////////////////////////////////////////////////////////
@ -133,50 +133,45 @@ class BotOptionsModel {
}
}
//TODO: define enum with all possible values
updateBotOption(String key, dynamic value) {
switch (key) {
case ModelKey.languageLevel:
languageLevel = value;
break;
case ModelKey.safetyModeration:
safetyModeration = value;
break;
case ModelKey.mode:
mode = value;
break;
case ModelKey.discussionTopic:
discussionTopic = value;
break;
case ModelKey.discussionKeywords:
discussionKeywords = value;
break;
case ModelKey.discussionTriggerReactionEnabled:
discussionTriggerReactionEnabled = value;
break;
case ModelKey.discussionTriggerReactionKey:
discussionTriggerReactionKey = value;
break;
case ModelKey.customSystemPrompt:
customSystemPrompt = value;
break;
case ModelKey.customTriggerReactionEnabled:
customTriggerReactionEnabled = value;
break;
case ModelKey.customTriggerReactionKey:
customTriggerReactionKey = value;
break;
case ModelKey.textAdventureGameMasterInstructions:
textAdventureGameMasterInstructions = value;
break;
case ModelKey.targetLanguage:
targetLanguage = value;
break;
case ModelKey.targetVoice:
targetVoice = value;
break;
default:
throw Exception('Invalid key for bot options - $key');
}
BotOptionsModel copyWith({
LanguageLevelTypeEnum? languageLevel,
String? topic,
List<String>? keywords,
bool? safetyModeration,
String? mode,
String? discussionTopic,
String? discussionKeywords,
bool? discussionTriggerReactionEnabled,
String? discussionTriggerReactionKey,
String? customSystemPrompt,
bool? customTriggerReactionEnabled,
String? customTriggerReactionKey,
String? textAdventureGameMasterInstructions,
String? targetLanguage,
String? targetVoice,
}) {
return BotOptionsModel(
languageLevel: languageLevel ?? this.languageLevel,
topic: topic ?? this.topic,
keywords: keywords ?? this.keywords,
safetyModeration: safetyModeration ?? this.safetyModeration,
mode: mode ?? this.mode,
discussionTopic: discussionTopic ?? this.discussionTopic,
discussionKeywords: discussionKeywords ?? this.discussionKeywords,
discussionTriggerReactionEnabled: discussionTriggerReactionEnabled ??
this.discussionTriggerReactionEnabled,
discussionTriggerReactionKey:
discussionTriggerReactionKey ?? this.discussionTriggerReactionKey,
customSystemPrompt: customSystemPrompt ?? this.customSystemPrompt,
customTriggerReactionEnabled:
customTriggerReactionEnabled ?? this.customTriggerReactionEnabled,
customTriggerReactionKey:
customTriggerReactionKey ?? this.customTriggerReactionKey,
textAdventureGameMasterInstructions:
textAdventureGameMasterInstructions ??
this.textAdventureGameMasterInstructions,
targetLanguage: targetLanguage ?? this.targetLanguage,
targetVoice: targetVoice ?? this.targetVoice,
);
}
}

View file

@ -148,6 +148,20 @@ class PangeaInvitationSelectionController
return parents.first;
}
bool get showInviteAllInSpaceButton {
final roomParticipants = participants;
if (roomParticipants == null ||
filter != InvitationFilter.space ||
spaceParent == null) {
return false;
}
final spaceParticipants = spaceParent!.getParticipants();
return spaceParticipants.any(
(participant) => !roomParticipants.any((p) => p.id == participant.id),
);
}
List<InvitationFilter> get availableFilters => InvitationFilter.values
.where(
(f) => switch (f) {

View file

@ -157,27 +157,34 @@ class PangeaInvitationSelectionView extends StatelessWidget {
final participants =
room.getParticipants().map((user) => user.id).toSet();
return controller.filter == InvitationFilter.public
? ListView.builder(
itemCount: controller.foundProfiles.length,
itemBuilder: (BuildContext context, int i) =>
_InviteContactListTile(
profile: controller.foundProfiles[i],
isMember: participants.contains(
controller.foundProfiles[i].userId,
),
onTap: () => controller.inviteAction(
controller.foundProfiles[i].userId,
),
controller: controller,
),
)
? controller.foundProfiles.isEmpty
? Padding(
padding: const EdgeInsets.all(24.0),
child: Text(
room.isSpace
? L10n.of(context).publicInviteDescSpace
: L10n.of(context).publicInviteDescChat,
),
)
: ListView.builder(
itemCount: controller.foundProfiles.length,
itemBuilder: (BuildContext context, int i) =>
_InviteContactListTile(
profile: controller.foundProfiles[i],
isMember: participants.contains(
controller.foundProfiles[i].userId,
),
onTap: () => controller.inviteAction(
controller.foundProfiles[i].userId,
),
controller: controller,
),
)
: ListView.builder(
itemCount: contacts.length + 2,
itemBuilder: (BuildContext context, int i) {
if (i == 0) {
return controller.filter ==
InvitationFilter.space &&
controller.spaceParent != null
return controller.showInviteAllInSpaceButton
? ListTile(
leading: ClipPath(
clipper: MapClipper(),

View file

@ -7,23 +7,19 @@ import 'package:fluffychat/pangea/chat/constants/default_power_level.dart';
import 'package:fluffychat/pangea/chat_settings/constants/bot_mode.dart';
import 'package:fluffychat/pangea/chat_settings/models/bot_options_model.dart';
import 'package:fluffychat/pangea/events/constants/pangea_event_types.dart';
import 'package:fluffychat/pangea/user/user_model.dart';
import 'package:fluffychat/widgets/matrix.dart';
extension BotClientExtension on Client {
bool get hasBotDM => rooms.any((r) => r.isBotDM);
Room? get botDM => rooms.firstWhereOrNull((r) => r.isBotDM);
Room? get botDM => rooms.firstWhereOrNull(
(room) {
if (room.isDirectChat &&
room.directChatMatrixID == BotName.byEnvironment) {
return true;
}
if (room.botOptions?.mode == BotMode.directChat) {
return true;
}
return false;
},
);
// All 2-member rooms with the bot
List<Room> get targetBotChats => rooms.where((r) {
if (r.isBotDM) return true;
if (r.summary.mJoinedMemberCount != 2) return false;
return r.getParticipants().any((u) => u.id == BotName.byEnvironment);
}).toList();
Future<String> startChatWithBot() => startDirectChat(
BotName.byEnvironment,
@ -45,27 +41,31 @@ extension BotClientExtension on Client {
],
);
Future<void> updateBotOptions() async {
if (!isLogged() || botDM == null) return;
Future<void> updateBotOptions(UserSettings userSettings) async {
final rooms = targetBotChats;
if (rooms.isEmpty) return;
final targetLanguage =
MatrixState.pangeaController.userController.userL2?.langCode;
final cefrLevel = MatrixState
.pangeaController.userController.profile.userSettings.cefrLevel;
final updateBotOptions = botDM!.botOptions ?? BotOptionsModel();
final futures = <Future>[];
for (final room in rooms) {
final botOptions = room.botOptions ?? const BotOptionsModel();
final targetLanguage = userSettings.targetLanguage;
final languageLevel = userSettings.cefrLevel;
final voice = userSettings.voice;
if (updateBotOptions.targetLanguage == targetLanguage &&
updateBotOptions.languageLevel == cefrLevel) {
return;
if (botOptions.targetLanguage == targetLanguage &&
botOptions.languageLevel == languageLevel &&
botOptions.targetVoice == voice) {
continue;
}
final updated = botOptions.copyWith(
targetLanguage: targetLanguage,
languageLevel: languageLevel,
targetVoice: voice,
);
futures.add(room.setBotOptions(updated));
}
if (targetLanguage != null &&
updateBotOptions.targetLanguage != targetLanguage) {
updateBotOptions.targetVoice = null;
}
updateBotOptions.targetLanguage = targetLanguage;
updateBotOptions.languageLevel = cefrLevel;
await botDM!.setBotOptions(updateBotOptions);
await Future.wait(futures);
}
}

View file

@ -1,92 +1,68 @@
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:dropdown_button2/dropdown_button2.dart';
import 'package:fluffychat/l10n/l10n.dart';
import 'package:fluffychat/pangea/common/widgets/dropdown_text_button.dart';
import 'package:fluffychat/pangea/learning_settings/language_level_type_enum.dart';
class LanguageLevelDropdown extends StatelessWidget {
final LanguageLevelTypeEnum? initialLevel;
final Function(LanguageLevelTypeEnum)? onChanged;
final FormFieldValidator<Object>? validator;
final bool enabled;
final Color? backgroundColor;
final double? width;
const LanguageLevelDropdown({
super.key,
this.initialLevel = LanguageLevelTypeEnum.a1,
this.onChanged,
this.validator,
this.enabled = true,
this.backgroundColor,
this.width,
});
@override
Widget build(BuildContext context) {
final l10n = L10n.of(context);
return DropdownButtonFormField2<LanguageLevelTypeEnum>(
customButton: initialLevel != null &&
LanguageLevelTypeEnum.values.contains(initialLevel)
? CustomDropdownTextButton(text: initialLevel!.title(context))
: null,
menuItemStyleData: const MenuItemStyleData(
padding: EdgeInsets.symmetric(
vertical: 8.0,
horizontal: 16.0,
return ButtonTheme(
alignedDropdown: true,
child: DropdownButtonFormField<LanguageLevelTypeEnum>(
itemHeight: null,
decoration: InputDecoration(
labelText: l10n.cefrLevelLabel,
),
height: 100.0,
),
decoration: InputDecoration(
labelText: l10n.cefrLevelLabel,
),
isExpanded: true,
dropdownStyleData: DropdownStyleData(
maxHeight: kIsWeb ? 500 : null,
decoration: BoxDecoration(
color: backgroundColor ??
Theme.of(context).colorScheme.surfaceContainerHigh,
borderRadius: BorderRadius.circular(14.0),
),
width: width,
),
items:
LanguageLevelTypeEnum.values.map((LanguageLevelTypeEnum levelOption) {
return DropdownMenuItem(
value: levelOption,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.spaceBetween,
mainAxisSize: MainAxisSize.min,
children: [
Text(levelOption.title(context)),
Flexible(
child: Text(
levelOption.description(context),
style: TextStyle(
color: Theme.of(context).colorScheme.onSurfaceVariant,
fontSize: 14,
selectedItemBuilder: (context) => LanguageLevelTypeEnum.values
.map((levelOption) => Text(levelOption.title(context)))
.toList(),
isExpanded: true,
dropdownColor: Theme.of(context).colorScheme.surfaceContainerHigh,
borderRadius: BorderRadius.circular(14.0),
onChanged: (value) {
if (value != null) onChanged?.call(value);
},
initialValue: initialLevel,
items: LanguageLevelTypeEnum.values
.map((LanguageLevelTypeEnum levelOption) {
return DropdownMenuItem(
value: levelOption,
child: Padding(
padding: const EdgeInsets.symmetric(vertical: 8.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.spaceBetween,
mainAxisSize: MainAxisSize.min,
children: [
Text(levelOption.title(context)),
Flexible(
child: Text(
levelOption.description(context),
style: TextStyle(
color: Theme.of(context).colorScheme.onSurfaceVariant,
fontSize: 14,
),
maxLines: 5,
overflow: TextOverflow.ellipsis,
),
),
maxLines: 5,
overflow: TextOverflow.ellipsis,
),
],
),
],
),
);
}).toList(),
onChanged: enabled
? (value) {
if (value != null) onChanged?.call(value);
}
: null,
value: initialLevel,
validator: validator,
enableFeedback: enabled,
),
);
}).toList(),
),
);
}
}

View file

@ -109,6 +109,7 @@ class ModelKey {
static const String transcription = "transcription";
static const String botTranscription = 'bot_transcription';
static const String voice = "voice";
// bot options
static const String languageLevel = "difficulty";

View file

@ -18,6 +18,7 @@ import 'package:fluffychat/pangea/languages/p_language_store.dart';
import 'package:fluffychat/pangea/subscription/controllers/subscription_controller.dart';
import 'package:fluffychat/pangea/text_to_speech/tts_controller.dart';
import 'package:fluffychat/pangea/user/pangea_push_rules_extension.dart';
import 'package:fluffychat/pangea/user/style_settings_repo.dart';
import 'package:fluffychat/pangea/user/user_controller.dart';
import 'package:fluffychat/widgets/matrix.dart';
import '../utils/firebase_analytics.dart';
@ -55,7 +56,7 @@ class PangeaController {
TtsController.setAvailableLanguages();
}
void _onLogin(BuildContext context) {
void _onLogin(BuildContext context, String? userID) {
initControllers();
_registerSubscriptions();
@ -64,6 +65,10 @@ class PangeaController {
Provider.of<LocaleProvider>(context, listen: false).setLocale(l1);
});
subscriptionController.reinitialize();
StyleSettingsRepo.fontSizeFactor(userID!).then((factor) {
AppConfig.fontSizeFactor = factor;
});
}
void _onLogout(BuildContext context) {
@ -91,7 +96,7 @@ class PangeaController {
_onLogout(context);
break;
case LoginState.loggedIn:
_onLogin(context);
_onLogin(context, userID);
break;
}
@ -112,8 +117,9 @@ class PangeaController {
userController.languageStream.stream.listen(_onLanguageUpdate);
_settingsSubscription?.cancel();
_settingsSubscription = userController.settingsUpdateStream.stream
.listen((_) => matrixState.client.updateBotOptions());
_settingsSubscription = userController.settingsUpdateStream.stream.listen(
(update) => matrixState.client.updateBotOptions(update.userSettings),
);
_joinSpaceSubscription?.cancel();
_joinSpaceSubscription ??= matrixState.client.onSync.stream
@ -122,9 +128,7 @@ class PangeaController {
}
Future<void> _clearCache({List<String> exclude = const []}) async {
final List<Future<void>> futures = [
matrixState.store.setString(SettingKeys.fontSizeFactor, ''),
];
final List<Future<void>> futures = [];
for (final key in _storageKeys) {
if (exclude.contains(key)) continue;
futures.add(GetStorage(key).erase());
@ -142,7 +146,6 @@ class PangeaController {
);
}
AppConfig.fontSizeFactor = 1.0;
await Future.wait(futures);
}
@ -174,7 +177,7 @@ class PangeaController {
}
_clearCache(exclude: exclude);
matrixState.client.updateBotOptions();
matrixState.client.updateBotOptions(userController.profile.userSettings);
}
static final List<String> _storageKeys = [

View file

@ -57,7 +57,9 @@ class CourseChatsController extends State<CourseChats>
@override
void initState() {
loadHierarchy(reload: true);
loadHierarchy(reload: true).then(
(_) => _joinDefaultChats(),
);
// Listen for changes to the activeSpace's hierarchy,
// and reload the hierarchy when they come through
@ -212,7 +214,6 @@ class CourseChatsController extends State<CourseChats>
try {
await _loadHierarchy(activeSpace: room, reload: reload);
if (mounted) await _joinDefaultChats();
if (mounted) {
final futures = [
loadRoomSummaries(
@ -437,7 +438,7 @@ class CourseChatsController extends State<CourseChats>
void joinChildRoom(SpaceRoomsChunk item) async {
final space = widget.client.getRoomById(widget.roomId);
final joined = await PublicRoomBottomSheet.show(
final roomId = await PublicRoomBottomSheet.show(
context: context,
chunk: item,
via: space?.spaceChildren
@ -446,10 +447,12 @@ class CourseChatsController extends State<CourseChats>
)
?.via,
);
if (mounted && joined == true) {
if (mounted && roomId != null) {
setState(() {
discoveredChildren?.remove(item);
});
NavigationUtil.goToSpaceRoute(roomId, [], context);
}
}

View file

@ -179,7 +179,7 @@ class SelectedCourseController extends State<SelectedCourse>
await showOkAlertDialog(
context: context,
title: L10n.of(context).youHaveKnocked,
message: L10n.of(context).pleaseWaitUntilInvited,
message: L10n.of(context).knockDesc,
);
return;
}

View file

@ -234,40 +234,43 @@ class SelectedCourseView extends StatelessWidget {
spacing: 8.0,
mainAxisSize: MainAxisSize.min,
children: [
Row(
spacing: 12.0,
children: [
const Icon(
Icons.edit,
size: mediumIconSize,
),
Flexible(
child: Text(
L10n.of(context).editCourseLater,
style: const TextStyle(
fontSize: descFontSize,
if (controller.widget.mode !=
SelectedCourseMode.join) ...[
Row(
spacing: 12.0,
children: [
const Icon(
Icons.edit,
size: mediumIconSize,
),
Flexible(
child: Text(
L10n.of(context).editCourseLater,
style: const TextStyle(
fontSize: descFontSize,
),
),
),
),
],
),
Row(
spacing: 12.0,
children: [
const Icon(
Icons.shield,
size: mediumIconSize,
),
Flexible(
child: Text(
L10n.of(context).newCourseAccess,
style: const TextStyle(
fontSize: descFontSize,
],
),
Row(
spacing: 12.0,
children: [
const Icon(
Icons.shield,
size: mediumIconSize,
),
Flexible(
child: Text(
L10n.of(context).newCourseAccess,
style: const TextStyle(
fontSize: descFontSize,
),
),
),
),
],
),
],
),
],
Padding(
padding: const EdgeInsets.only(top: 8.0),
child: ElevatedButton(

View file

@ -114,7 +114,7 @@ class PangeaMessageEvent {
.map(
(e) => RepresentationEvent(
event: e,
parentMessageEvent: _event,
parentMessageEvent: _latestEdit,
timeline: timeline,
),
)
@ -181,14 +181,14 @@ class PangeaMessageEvent {
final original = PangeaRepresentation(
langCode: lang ?? LanguageKeys.unknownLanguage,
text: _event.body,
text: _latestEdit.body,
originalSent: true,
originalWritten: false,
);
_representations!.add(
RepresentationEvent(
parentMessageEvent: _event,
parentMessageEvent: _latestEdit,
content: original,
tokens: tokens,
choreo: _embeddedChoreo,
@ -202,7 +202,7 @@ class PangeaMessageEvent {
e: err,
s: s,
data: {
"event": _event.toJson(),
"event": _latestEdit.toJson(),
},
);
}
@ -211,7 +211,7 @@ class PangeaMessageEvent {
try {
_representations!.add(
RepresentationEvent(
parentMessageEvent: _event,
parentMessageEvent: _latestEdit,
content: PangeaRepresentation.fromJson(
_latestEdit.content[ModelKey.originalWritten]
as Map<String, dynamic>,
@ -229,7 +229,7 @@ class PangeaMessageEvent {
e: err,
s: s,
data: {
"event": _event.toJson(),
"event": _latestEdit.toJson(),
},
);
}
@ -278,7 +278,8 @@ class PangeaMessageEvent {
/// Gets the message display text for the current language code.
/// If the message display text is not available for the current language code,
/// it returns the message body.
String get messageDisplayText => messageDisplayRepresentation?.text ?? body;
String get messageDisplayText =>
messageDisplayRepresentation?.text ?? _latestEdit.body;
TextDirection get textDirection =>
LanguageConstants.rtlLanguageCodes.contains(messageDisplayLangCode)
@ -300,7 +301,11 @@ class PangeaMessageEvent {
(filter?.call(element) ?? true),
);
Event? getTextToSpeechLocal(String langCode, String text) {
Event? getTextToSpeechLocal(
String langCode,
String text,
String? voice,
) {
for (final audio in allAudio) {
final dataMap = audio.content.tryGetMap(ModelKey.transcription);
if (dataMap == null || !dataMap.containsKey(ModelKey.tokens)) continue;
@ -310,7 +315,9 @@ class PangeaMessageEvent {
dataMap as dynamic,
);
if (audioData.langCode == langCode && audioData.text == text) {
if (audioData.langCode == langCode &&
audioData.text == text &&
audioData.voice == voice) {
return audio;
}
} catch (e, s) {
@ -365,7 +372,7 @@ class PangeaMessageEvent {
String langCode,
String? voice,
) async {
final local = getTextToSpeechLocal(langCode, messageDisplayText);
final local = getTextToSpeechLocal(langCode, messageDisplayText, voice);
if (local != null) {
final file = await local.getPangeaAudioFile();
if (file != null) return file;
@ -421,7 +428,7 @@ class PangeaMessageEvent {
'waveform': response.waveform,
},
ModelKey.transcription: response
.toPangeaAudioEventData(rep?.text ?? body, langCode)
.toPangeaAudioEventData(rep?.text ?? body, langCode, voice)
.toJson(),
},
).then((eventId) async {
@ -492,7 +499,7 @@ class PangeaMessageEvent {
_representations = null;
return room.sendPangeaEvent(
content: representation.toJson(),
parentEventId: eventId,
parentEventId: _latestEdit.eventId,
type: PangeaEventTypes.representation,
);
}
@ -586,6 +593,7 @@ class PangeaMessageEvent {
}
Future<String> requestRespresentationByL1() async {
debugPrint("LATEST EDIT: ${_latestEdit.toJson()}");
if (_l1Code == null || _l2Code == null) {
throw Exception("Missing language codes");
}
@ -597,7 +605,9 @@ class PangeaMessageEvent {
RepresentationEvent? rep;
if (!includedIT) {
// if the message didn't go through translation, get any l1 rep
debugPrint("REPRESENTATIONS: ${representations.length}");
rep = representationByLanguage(_l1Code!);
debugPrint("REP: $rep");
} else {
// if the message went through translation, get the non-original
// l1 rep since originalWritten could contain some l2 words
@ -614,6 +624,13 @@ class PangeaMessageEvent {
? (originalWritten?.langCode ?? _l1Code!)
: (originalSent?.langCode ?? _l2Code!);
debugPrint("Original written content: $originalWrittenContent");
debugPrint("Message display text: $messageDisplayText");
debugPrint("Original sent: ${originalSent?.content.toJson()}");
debugPrint(
"Message display rep: ${representationByLanguage(messageDisplayLangCode)?.content.toJson()}",
);
final resp = await _requestRepresentation(
includedIT ? originalWrittenContent : messageDisplayText,
_l1Code!,
@ -661,7 +678,7 @@ class PangeaMessageEvent {
) async {
final repEvent = await room.sendPangeaEvent(
content: representation.toJson(),
parentEventId: eventId,
parentEventId: _latestEdit.eventId,
type: PangeaEventTypes.representation,
);
return repEvent?.eventId;

View file

@ -134,25 +134,28 @@ class InlineTooltipState extends State<InlineTooltip>
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisSize: MainAxisSize.min,
children: [
Icon(
Icons.lightbulb,
size: 20,
color: Theme.of(context).colorScheme.onSurface,
Padding(
padding: const EdgeInsets.only(right: 6.0),
child: Icon(
Icons.lightbulb,
size: 20,
color: Theme.of(context).colorScheme.onSurface,
),
),
const SizedBox(width: 8),
Flexible(
child: Center(
child: Text(
widget.message,
style: widget.textStyle ??
(FluffyThemes.isColumnMode(context)
? Theme.of(context).textTheme.titleLarge
: Theme.of(context).textTheme.bodyLarge),
? Theme.of(context).textTheme.titleSmall
: Theme.of(context).textTheme.bodyMedium),
textAlign: TextAlign.center,
),
),
),
IconButton(
padding: const EdgeInsets.only(left: 6.0),
constraints: const BoxConstraints(),
icon: Icon(
Icons.close_outlined,

View file

@ -5,14 +5,10 @@ class LanguageMismatchRepo {
static const Duration displayInterval = Duration(minutes: 30);
static String _roomKey(String roomId) => 'language_mismatch_room_$roomId';
static String _eventKey(String eventId) => 'language_mismatch_event_$eventId';
static bool shouldShowByRoom(String roomId) => _get(_roomKey(roomId));
static bool shouldShowByEvent(String eventId) => _get(_eventKey(eventId));
static Future<void> setRoom(String roomId) async => _set(_roomKey(roomId));
static Future<void> setEvent(String eventId) async =>
_set(_eventKey(eventId));
static Future<void> _set(String key) async {
await _storage.write(

View file

@ -44,7 +44,6 @@ class PSettingsSwitchListTileState
@override
Widget build(BuildContext context) {
return SwitchListTile.adaptive(
contentPadding: EdgeInsets.zero,
value: currentValue,
title: Text(widget.title),
activeThumbColor: AppConfig.activeToggleColor,

View file

@ -41,7 +41,6 @@ class SettingsLearningController extends State<SettingsLearning> {
PangeaController pangeaController = MatrixState.pangeaController;
late Profile _profile;
final GlobalKey<FormState> formKey = GlobalKey<FormState>();
String? languageMatchError;
final ScrollController scrollController = ScrollController();
@ -110,18 +109,16 @@ class SettingsLearningController extends State<SettingsLearning> {
updateToolSetting(ToolSetting.enableTTS, false);
}
if (formKey.currentState!.validate()) {
await showFutureLoadingDialog(
context: context,
future: () async => pangeaController.userController
.updateProfile(
(_) => _profile,
waitForDataInSync: true,
)
.timeout(const Duration(seconds: 15)),
);
Navigator.of(context).pop();
}
await showFutureLoadingDialog(
context: context,
future: () async => pangeaController.userController
.updateProfile(
(_) => _profile,
waitForDataInSync: true,
)
.timeout(const Duration(seconds: 15)),
);
Navigator.of(context).pop();
}
Future<void> resetInstructionTooltips() async {
@ -153,11 +150,12 @@ class SettingsLearningController extends State<SettingsLearning> {
LanguageModel? sourceLanguage,
LanguageModel? targetLanguage,
}) async {
if (sourceLanguage != null) {
if (sourceLanguage != null && sourceLanguage != selectedSourceLanguage) {
_profile.userSettings.sourceLanguage = sourceLanguage.langCode;
}
if (targetLanguage != null) {
if (targetLanguage != null && targetLanguage != selectedTargetLanguage) {
_profile.userSettings.targetLanguage = targetLanguage.langCode;
_profile.userSettings.voice = null;
if (!_profile.toolSettings.enableTTS && isTTSSupported) {
updateToolSetting(ToolSetting.enableTTS, true);
}
@ -181,6 +179,11 @@ class SettingsLearningController extends State<SettingsLearning> {
if (mounted) setState(() {});
}
void setVoice(String? voice) {
_profile.userSettings.voice = voice;
if (mounted) setState(() {});
}
void changeCountry(Country? country) {
_profile.userSettings.country = country?.name;
if (mounted) setState(() {});
@ -343,6 +346,8 @@ class SettingsLearningController extends State<SettingsLearning> {
LanguageLevelTypeEnum get cefrLevel => _profile.userSettings.cefrLevel;
String? get selectedVoice => _profile.userSettings.voice;
Country? get country =>
CountryService().findByName(_profile.userSettings.country);

View file

@ -1,7 +1,6 @@
import 'package:flutter/material.dart';
import 'package:fluffychat/config/app_config.dart';
import 'package:fluffychat/config/themes.dart';
import 'package:fluffychat/l10n/l10n.dart';
import 'package:fluffychat/pangea/chat_settings/widgets/language_level_dropdown.dart';
import 'package:fluffychat/pangea/common/constants/model_keys.dart';
@ -14,6 +13,7 @@ import 'package:fluffychat/pangea/learning_settings/p_language_dropdown.dart';
import 'package:fluffychat/pangea/learning_settings/p_settings_switch_list_tile.dart';
import 'package:fluffychat/pangea/learning_settings/settings_learning.dart';
import 'package:fluffychat/pangea/learning_settings/tool_settings_enum.dart';
import 'package:fluffychat/pangea/learning_settings/voice_dropdown.dart';
import 'package:fluffychat/widgets/layouts/max_width_body.dart';
import 'package:fluffychat/widgets/matrix.dart';
@ -45,21 +45,23 @@ class SettingsLearningView extends StatelessWidget {
)
: null,
),
body: Form(
key: controller.formKey,
child: ListTileTheme(
iconColor: Theme.of(context).textTheme.bodyLarge!.color,
child: MaxWidthBody(
withScrolling: false,
child: Column(
children: [
Expanded(
child: SingleChildScrollView(
controller: controller.scrollController,
body: ListTileTheme(
iconColor: Theme.of(context).textTheme.bodyLarge!.color,
child: MaxWidthBody(
withScrolling: false,
child: Column(
children: [
Expanded(
child: SingleChildScrollView(
controller: controller.scrollController,
child: Padding(
padding: const EdgeInsets.symmetric(vertical: 32.0),
child: Column(
spacing: 16.0,
children: [
Padding(
padding: const EdgeInsets.all(16.0),
padding:
const EdgeInsets.symmetric(horizontal: 16.0),
child: Column(
spacing: 16.0,
children: [
@ -99,171 +101,112 @@ class SettingsLearningView extends StatelessWidget {
.colorScheme
.surfaceContainerHigh,
),
AnimatedSize(
duration: FluffyThemes.animationDuration,
curve: FluffyThemes.animationCurve,
child: controller.userL1?.langCodeShort ==
controller.userL2?.langCodeShort
? Padding(
padding: const EdgeInsets.symmetric(
horizontal: 16.0,
if (controller.userL1?.langCodeShort ==
controller.userL2?.langCodeShort)
Padding(
padding: const EdgeInsets.symmetric(
horizontal: 16.0,
),
child: Row(
spacing: 8.0,
children: [
Icon(
Icons.info_outlined,
color: Theme.of(context)
.colorScheme
.error,
),
Flexible(
child: Text(
L10n.of(context)
.noIdenticalLanguages,
style: TextStyle(
color: Theme.of(context)
.colorScheme
.error,
),
),
child: Row(
spacing: 8.0,
children: [
Icon(
Icons.info_outlined,
color: Theme.of(context)
.colorScheme
.error,
),
Flexible(
child: Text(
L10n.of(context)
.noIdenticalLanguages,
style: TextStyle(
color: Theme.of(context)
.colorScheme
.error,
),
),
),
],
),
)
: const SizedBox.shrink(),
),
CountryPickerDropdown(controller),
),
],
),
),
LanguageLevelDropdown(
initialLevel: controller.cefrLevel,
onChanged: controller.setCefrLevel,
),
VoiceDropdown(
value: controller.selectedVoice,
language: controller.selectedTargetLanguage,
onChanged: controller.setVoice,
),
CountryPickerDropdown(controller),
GenderDropdown(
initialGender: controller.gender,
onChanged: controller.setGender,
),
Container(
decoration: BoxDecoration(
border: Border.all(
color: Colors.white54,
),
borderRadius: BorderRadius.circular(8.0),
),
padding: const EdgeInsets.all(8.0),
child: Column(
children: [
ProfileSettingsSwitchListTile.adaptive(
defaultValue:
controller.getToolSetting(
ToolSetting.autoIGC,
),
title: ToolSetting.autoIGC
.toolName(context),
subtitle: ToolSetting.autoIGC
.toolDescription(context),
onChange: (bool value) =>
controller.updateToolSetting(
ToolSetting.autoIGC,
value,
),
enabled: true,
),
ProfileSettingsSwitchListTile.adaptive(
defaultValue:
controller.getToolSetting(
ToolSetting.enableAutocorrect,
),
title: ToolSetting.enableAutocorrect
.toolName(context),
subtitle: ToolSetting
.enableAutocorrect
.toolDescription(context),
onChange: (bool value) {
controller.updateToolSetting(
ToolSetting.enableAutocorrect,
value,
);
if (value) {
controller
.showKeyboardSettingsDialog();
}
},
enabled: true,
),
],
),
),
for (final toolSetting
in ToolSetting.values.where(
(tool) =>
tool.isAvailableSetting &&
tool != ToolSetting.autoIGC &&
tool != ToolSetting.enableAutocorrect,
))
Column(
children: [
ProfileSettingsSwitchListTile.adaptive(
defaultValue: controller
.getToolSetting(toolSetting),
title: toolSetting.toolName(context),
subtitle: toolSetting ==
ToolSetting.enableTTS &&
!controller.isTTSSupported
? null
: toolSetting
.toolDescription(context),
onChange: (bool value) =>
controller.updateToolSetting(
toolSetting,
value,
),
),
],
),
SwitchListTile.adaptive(
value: controller.publicProfile,
onChanged: controller.setPublicProfile,
title: Text(
L10n.of(context).publicProfileTitle,
),
subtitle: Text(
L10n.of(context).publicProfileDesc,
),
activeThumbColor:
AppConfig.activeToggleColor,
contentPadding: EdgeInsets.zero,
),
ResetInstructionsListTile(
controller: controller,
),
],
),
),
...ToolSetting.values
.where(
(tool) => tool.isAvailableSetting,
)
.map(
(toolSetting) => _ProfileSwitchTile(
value:
controller.getToolSetting(toolSetting),
setting: toolSetting,
onChanged: (v) {
controller.updateToolSetting(
toolSetting,
v,
);
if (v &&
toolSetting ==
ToolSetting.enableTTS) {
controller.showKeyboardSettingsDialog();
}
},
),
),
SwitchListTile.adaptive(
value: controller.publicProfile,
onChanged: controller.setPublicProfile,
title: Text(
L10n.of(context).publicProfileTitle,
),
subtitle: Text(
L10n.of(context).publicProfileDesc,
),
activeThumbColor: AppConfig.activeToggleColor,
),
ResetInstructionsListTile(
controller: controller,
),
],
),
),
),
Padding(
padding: const EdgeInsets.all(16.0),
child: SizedBox(
width: double.infinity,
child: ElevatedButton(
onPressed: controller.haveSettingsBeenChanged
? controller.submit
: null,
child: Text(L10n.of(context).saveChanges),
),
),
Padding(
padding: const EdgeInsets.all(16.0),
child: SizedBox(
width: double.infinity,
child: ElevatedButton(
onPressed: controller.haveSettingsBeenChanged
? controller.submit
: null,
child: Text(L10n.of(context).saveChanges),
),
),
],
),
),
],
),
),
),
);
if (!controller.widget.isDialog) return dialogContent;
return FullWidthDialog(
dialogContent: dialogContent,
maxWidth: 600,
@ -273,3 +216,25 @@ class SettingsLearningView extends StatelessWidget {
);
}
}
class _ProfileSwitchTile extends StatelessWidget {
final bool value;
final ToolSetting setting;
final Function(bool) onChanged;
const _ProfileSwitchTile({
required this.value,
required this.setting,
required this.onChanged,
});
@override
Widget build(BuildContext context) {
return ProfileSettingsSwitchListTile.adaptive(
defaultValue: value,
title: setting.toolName(context),
subtitle: setting.toolDescription(context),
onChange: onChanged,
);
}
}

View file

@ -0,0 +1,53 @@
import 'package:flutter/material.dart';
import 'package:dropdown_button2/dropdown_button2.dart';
import 'package:fluffychat/l10n/l10n.dart';
import 'package:fluffychat/pangea/common/widgets/dropdown_text_button.dart';
import 'package:fluffychat/pangea/languages/language_model.dart';
class VoiceDropdown extends StatelessWidget {
final String? value;
final LanguageModel? language;
final Function(String?) onChanged;
const VoiceDropdown({
super.key,
this.value,
this.language,
required this.onChanged,
});
@override
Widget build(BuildContext context) {
return DropdownButtonFormField2<String>(
customButton:
value != null ? CustomDropdownTextButton(text: value!) : null,
menuItemStyleData: const MenuItemStyleData(
padding: EdgeInsets.symmetric(
vertical: 8.0,
horizontal: 16.0,
),
),
decoration: InputDecoration(
labelText: L10n.of(context).voiceDropdownTitle,
),
isExpanded: true,
dropdownStyleData: DropdownStyleData(
maxHeight: 250,
decoration: BoxDecoration(
color: Theme.of(context).colorScheme.surfaceContainerHigh,
borderRadius: BorderRadius.circular(14.0),
),
),
items: (language?.voices ?? <String>[]).map((voice) {
return DropdownMenuItem(
value: voice,
child: Text(voice),
);
}).toList(),
onChanged: onChanged,
value: value,
);
}
}

View file

@ -6,7 +6,7 @@ import 'package:go_router/go_router.dart';
import 'package:fluffychat/config/app_config.dart';
import 'package:fluffychat/l10n/l10n.dart';
import 'package:fluffychat/pangea/join_codes/space_code_controller.dart';
import 'package:fluffychat/pangea/login/pages/add_course_page.dart';
import 'package:fluffychat/pangea/spaces/space_constants.dart';
import 'package:fluffychat/widgets/matrix.dart';
class CourseCodePage extends StatefulWidget {
@ -72,7 +72,7 @@ class CourseCodePageState extends State<CourseCodePage> {
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
SvgPicture.network(
"${AppConfig.assetsBaseURL}/${AddCoursePage.mapUnlockFileName}",
"${AppConfig.assetsBaseURL}/${SpaceConstants.mapUnlockFileName}",
width: 100.0,
height: 100.0,
colorFilter: ColorFilter.mode(

Some files were not shown because too many files have changed in this diff Show more