Merge branch 'main' into 5244-grammar-practice-ui-updates
This commit is contained in:
commit
cae69a6d34
104 changed files with 3792 additions and 807 deletions
|
|
@ -9,6 +9,7 @@ import 'package:matrix/matrix_api_lite/generated/model.dart';
|
|||
|
||||
import 'package:fluffychat/config/app_config.dart';
|
||||
import 'package:fluffychat/config/themes.dart';
|
||||
import 'package:fluffychat/l10n/l10n.dart';
|
||||
import 'package:fluffychat/pages/archive/archive.dart';
|
||||
import 'package:fluffychat/pages/chat/chat.dart';
|
||||
import 'package:fluffychat/pages/chat_access_settings/chat_access_settings_controller.dart';
|
||||
|
|
@ -60,6 +61,7 @@ import 'package:fluffychat/pangea/login/pages/signup.dart';
|
|||
import 'package:fluffychat/pangea/space_analytics/space_analytics.dart';
|
||||
import 'package:fluffychat/pangea/spaces/space_constants.dart';
|
||||
import 'package:fluffychat/pangea/subscription/pages/settings_subscription.dart';
|
||||
import 'package:fluffychat/widgets/adaptive_dialogs/show_ok_cancel_alert_dialog.dart';
|
||||
import 'package:fluffychat/widgets/config_viewer.dart';
|
||||
import 'package:fluffychat/widgets/layouts/empty_page.dart';
|
||||
import 'package:fluffychat/widgets/layouts/two_column_layout.dart';
|
||||
|
|
@ -597,6 +599,24 @@ abstract class AppRoutes {
|
|||
),
|
||||
);
|
||||
},
|
||||
onExit: (context, state) async {
|
||||
// Check if bypass flag was set before navigation
|
||||
if (AnalyticsPractice.bypassExitConfirmation) {
|
||||
AnalyticsPractice.bypassExitConfirmation = false;
|
||||
return true;
|
||||
}
|
||||
|
||||
final result = await showOkCancelAlertDialog(
|
||||
useRootNavigator: false,
|
||||
context: context,
|
||||
title: L10n.of(context).areYouSure,
|
||||
okLabel: L10n.of(context).yes,
|
||||
cancelLabel: L10n.of(context).cancel,
|
||||
message: L10n.of(context).exitPractice,
|
||||
);
|
||||
|
||||
return result == OkCancelResult.ok;
|
||||
},
|
||||
),
|
||||
GoRoute(
|
||||
path: ':construct',
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"@@locale": "ar",
|
||||
"@@last_modified": "2026-01-13 15:17:18.289687",
|
||||
"@@last_modified": "2026-01-20 12:31:24.671375",
|
||||
"about": "حول",
|
||||
"@about": {
|
||||
"type": "String",
|
||||
|
|
@ -11057,5 +11057,55 @@
|
|||
"@webDownloadPermissionMessage": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"exitPractice": "لن يتم حفظ تقدم جلسة التدريب الخاصة بك.",
|
||||
"practiceGrammar": "تدرب على القواعد",
|
||||
"notEnoughToPractice": "أرسل المزيد من الرسائل لفتح التدريب",
|
||||
"constructUseCorGCDesc": "تدريب على فئة القواعد الصحيحة",
|
||||
"constructUseIncGCDesc": "تدريب على فئة القواعد غير الصحيحة",
|
||||
"@exitPractice": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"@practiceGrammar": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"@notEnoughToPractice": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"@constructUseCorGCDesc": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"@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": {}
|
||||
}
|
||||
}
|
||||
|
|
@ -1911,7 +1911,7 @@
|
|||
"playWithAI": "Пакуль гуляйце з ШІ",
|
||||
"courseStartDesc": "Pangea Bot гатовы да працы ў любы час!\n\n...але навучанне лепш з сябрамі!",
|
||||
"@@locale": "be",
|
||||
"@@last_modified": "2026-01-13 15:17:06.767898",
|
||||
"@@last_modified": "2026-01-20 12:31:06.570011",
|
||||
"@alwaysUse24HourFormat": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
|
|
@ -11939,5 +11939,55 @@
|
|||
"@webDownloadPermissionMessage": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"exitPractice": "Ваш практычны сеанс не будзе захаваны.",
|
||||
"practiceGrammar": "Практыкаваць граматыку",
|
||||
"notEnoughToPractice": "Адпраўце больш паведамленняў, каб разблакаваць практыку",
|
||||
"constructUseCorGCDesc": "Практыка ў катэгорыі правільнай граматыкі",
|
||||
"constructUseIncGCDesc": "Практыка ў катэгорыі няправільнай граматыкі",
|
||||
"@exitPractice": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"@practiceGrammar": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"@notEnoughToPractice": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"@constructUseCorGCDesc": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"@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": {}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
{
|
||||
"@@last_modified": "2026-01-13 15:17:32.418851",
|
||||
"@@last_modified": "2026-01-20 12:31:49.464406",
|
||||
"about": "সম্পর্কে",
|
||||
"@about": {
|
||||
"type": "String",
|
||||
|
|
@ -11944,5 +11944,55 @@
|
|||
"@webDownloadPermissionMessage": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"exitPractice": "আপনার অনুশীলন সেশনের অগ্রগতি সংরক্ষিত হবে না।",
|
||||
"practiceGrammar": "ব্যাকরণ অনুশীলন করুন",
|
||||
"notEnoughToPractice": "অনুশীলন আনলক করতে আরও বার্তা পাঠান",
|
||||
"constructUseCorGCDesc": "সঠিক ব্যাকরণ বিভাগ অনুশীলন",
|
||||
"constructUseIncGCDesc": "ভুল ব্যাকরণ বিভাগ অনুশীলন",
|
||||
"@exitPractice": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"@practiceGrammar": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"@notEnoughToPractice": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"@constructUseCorGCDesc": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"@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": {}
|
||||
}
|
||||
}
|
||||
|
|
@ -4279,7 +4279,7 @@
|
|||
"joinPublicTrip": "མི་ཚེས་ལ་ལོག་འབད།",
|
||||
"startOwnTrip": "ངེད་རང་གི་ལོག་ལ་སྦྱོར་བཅོས།",
|
||||
"@@locale": "bo",
|
||||
"@@last_modified": "2026-01-13 15:17:29.552592",
|
||||
"@@last_modified": "2026-01-20 12:31:44.969872",
|
||||
"@alwaysUse24HourFormat": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
|
|
@ -10594,5 +10594,55 @@
|
|||
"@webDownloadPermissionMessage": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"exitPractice": "Ndae bɔkɔɔ a wopɛ no, wo nsɛm a wopɛ sɛ woyɛ no bɛyɛ a, ɛrenyɛ.",
|
||||
"practiceGrammar": "Bɔ mmara",
|
||||
"notEnoughToPractice": "Sɛ wopɛ sɛ woyɛ bɔ mmara a, fa nsɛm pii to mu",
|
||||
"constructUseCorGCDesc": "Nokware mmara kategorie bɔ mmara",
|
||||
"constructUseIncGCDesc": "Nnokwa mmara kategorie bɔ mmara",
|
||||
"@exitPractice": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"@practiceGrammar": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"@notEnoughToPractice": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"@constructUseCorGCDesc": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"@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": {}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
{
|
||||
"@@last_modified": "2026-01-13 15:17:08.493897",
|
||||
"@@last_modified": "2026-01-20 12:31:09.126351",
|
||||
"about": "Quant a",
|
||||
"@about": {
|
||||
"type": "String",
|
||||
|
|
@ -10864,5 +10864,55 @@
|
|||
"@webDownloadPermissionMessage": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"exitPractice": "El teu progrés de la sessió de pràctica no es desarà.",
|
||||
"practiceGrammar": "Practica gramàtica",
|
||||
"notEnoughToPractice": "Envia més missatges per desbloquejar la pràctica",
|
||||
"constructUseCorGCDesc": "Pràctica de la categoria de gramàtica correcta",
|
||||
"constructUseIncGCDesc": "Pràctica de la categoria de gramàtica incorrecta",
|
||||
"@exitPractice": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"@practiceGrammar": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"@notEnoughToPractice": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"@constructUseCorGCDesc": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"@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": {}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"@@locale": "cs",
|
||||
"@@last_modified": "2026-01-13 15:17:03.339822",
|
||||
"@@last_modified": "2026-01-20 12:31:00.323945",
|
||||
"about": "O aplikaci",
|
||||
"@about": {
|
||||
"type": "String",
|
||||
|
|
@ -11447,5 +11447,55 @@
|
|||
"@webDownloadPermissionMessage": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"exitPractice": "Pokrok vaší cvičební relace nebude uložen.",
|
||||
"practiceGrammar": "Cvičit gramatiku",
|
||||
"notEnoughToPractice": "Odešlete více zpráv, abyste odemkli cvičení",
|
||||
"constructUseCorGCDesc": "Cvičení správné gramatické kategorie",
|
||||
"constructUseIncGCDesc": "Cvičení nesprávné gramatické kategorie",
|
||||
"@exitPractice": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"@practiceGrammar": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"@notEnoughToPractice": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"@constructUseCorGCDesc": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"@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": {}
|
||||
}
|
||||
}
|
||||
|
|
@ -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-13 15:16:30.904012",
|
||||
"@@last_modified": "2026-01-20 12:30:10.761209",
|
||||
"@aboutHomeserver": {
|
||||
"type": "String",
|
||||
"placeholders": {
|
||||
|
|
@ -11901,5 +11901,55 @@
|
|||
"@webDownloadPermissionMessage": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"exitPractice": "Din praksis session fremskridt vil ikke blive gemt.",
|
||||
"practiceGrammar": "Øv grammatik",
|
||||
"notEnoughToPractice": "Send flere beskeder for at låse op for praksis",
|
||||
"constructUseCorGCDesc": "Korrekt grammatik kategori praksis",
|
||||
"constructUseIncGCDesc": "Ukorrrekt grammatik kategori praksis",
|
||||
"@exitPractice": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"@practiceGrammar": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"@notEnoughToPractice": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"@constructUseCorGCDesc": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"@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": {}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"@@locale": "de",
|
||||
"@@last_modified": "2026-01-13 15:16:52.685007",
|
||||
"@@last_modified": "2026-01-20 12:30:47.434524",
|
||||
"alwaysUse24HourFormat": "true",
|
||||
"@alwaysUse24HourFormat": {
|
||||
"description": "Set to true to always display time of day in 24 hour format."
|
||||
|
|
@ -10847,5 +10847,55 @@
|
|||
"@webDownloadPermissionMessage": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"exitPractice": "Ihr Fortschritt in der Übungssitzung wird nicht gespeichert.",
|
||||
"practiceGrammar": "Grammatik üben",
|
||||
"notEnoughToPractice": "Senden Sie mehr Nachrichten, um die Übung freizuschalten",
|
||||
"constructUseCorGCDesc": "Übung der korrekten Grammatikkategorie",
|
||||
"constructUseIncGCDesc": "Übung der inkorrekten Grammatikkategorie",
|
||||
"@exitPractice": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"@practiceGrammar": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"@notEnoughToPractice": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"@constructUseCorGCDesc": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"@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": {}
|
||||
}
|
||||
}
|
||||
|
|
@ -4456,7 +4456,7 @@
|
|||
"playWithAI": "Παίξτε με την Τεχνητή Νοημοσύνη προς το παρόν",
|
||||
"courseStartDesc": "Ο Pangea Bot είναι έτοιμος να ξεκινήσει οποιαδήποτε στιγμή!\n\n...αλλά η μάθηση είναι καλύτερη με φίλους!",
|
||||
"@@locale": "el",
|
||||
"@@last_modified": "2026-01-13 15:17:39.060670",
|
||||
"@@last_modified": "2026-01-20 12:31:59.503296",
|
||||
"@alwaysUse24HourFormat": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
|
|
@ -11898,5 +11898,55 @@
|
|||
"@webDownloadPermissionMessage": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"exitPractice": "Η πρόοδος της συνεδρίας πρακτικής σας δεν θα αποθηκευτεί.",
|
||||
"practiceGrammar": "Πρακτική γραμματικής",
|
||||
"notEnoughToPractice": "Στείλτε περισσότερα μηνύματα για να ξεκλειδώσετε την πρακτική",
|
||||
"constructUseCorGCDesc": "Πρακτική κατηγορίας σωστής γραμματικής",
|
||||
"constructUseIncGCDesc": "Πρακτική κατηγορίας λανθαστής γραμματικής",
|
||||
"@exitPractice": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"@practiceGrammar": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"@notEnoughToPractice": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"@constructUseCorGCDesc": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"@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": {}
|
||||
}
|
||||
}
|
||||
|
|
@ -5047,8 +5047,14 @@
|
|||
"youLeftTheChat": "🚪 You left the chat",
|
||||
"downloadInitiated": "Download initiated",
|
||||
"webDownloadPermissionMessage": "If your browser blocks downloads, please enable downloads for this site.",
|
||||
"exitPractice": "Your practice session progress won't be saved.",
|
||||
"practiceGrammar": "Practice grammar",
|
||||
"notEnoughToPractice": "Send more messages to unlock practice",
|
||||
"constructUseCorGCDesc": "Correct grammar category practice",
|
||||
"constructUseIncGCDesc": "Incorrect grammar category practice"
|
||||
"constructUseIncGCDesc": "Incorrect grammar category practice",
|
||||
"constructUseCorGEDesc": "Correct grammar error practice",
|
||||
"constructUseIncGEDesc": "Incorrect grammar error practice",
|
||||
"fillInBlank": "Fill in the blank with the correct choice",
|
||||
"learn": "Learn",
|
||||
"languageUpdated": "Target language updated!"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
{
|
||||
"@@last_modified": "2026-01-13 15:17:43.889996",
|
||||
"@@last_modified": "2026-01-20 12:32:07.490560",
|
||||
"about": "Prio",
|
||||
"@about": {
|
||||
"type": "String",
|
||||
|
|
@ -11929,5 +11929,55 @@
|
|||
"@webDownloadPermissionMessage": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"exitPractice": "Via praktika sesio progreso ne estos konservita.",
|
||||
"practiceGrammar": "Praktiku gramatikon",
|
||||
"notEnoughToPractice": "Sendu pli da mesaĝoj por malŝlosi praktikon",
|
||||
"constructUseCorGCDesc": "Praktiko de ĝusta gramatika kategorio",
|
||||
"constructUseIncGCDesc": "Praktiko de malĝusta gramatika kategorio",
|
||||
"@exitPractice": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"@practiceGrammar": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"@notEnoughToPractice": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"@constructUseCorGCDesc": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"@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": {}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"@@locale": "es",
|
||||
"@@last_modified": "2026-01-13 15:16:25.022581",
|
||||
"@@last_modified": "2026-01-20 12:29:59.898184",
|
||||
"about": "Acerca de",
|
||||
"@about": {
|
||||
"type": "String",
|
||||
|
|
@ -8074,5 +8074,55 @@
|
|||
"@webDownloadPermissionMessage": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"exitPractice": "El progreso de tu sesión de práctica no se guardará.",
|
||||
"practiceGrammar": "Practicar gramática",
|
||||
"notEnoughToPractice": "Envía más mensajes para desbloquear la práctica",
|
||||
"constructUseCorGCDesc": "Práctica de categoría de gramática correcta",
|
||||
"constructUseIncGCDesc": "Práctica de categoría de gramática incorrecta",
|
||||
"@exitPractice": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"@practiceGrammar": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"@notEnoughToPractice": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"@constructUseCorGCDesc": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"@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": {}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"@@locale": "et",
|
||||
"@@last_modified": "2026-01-13 15:16:50.842106",
|
||||
"@@last_modified": "2026-01-20 12:30:45.162738",
|
||||
"about": "Rakenduse teave",
|
||||
"@about": {
|
||||
"type": "String",
|
||||
|
|
@ -11111,5 +11111,55 @@
|
|||
"@webDownloadPermissionMessage": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"exitPractice": "Teie harjut seansi edusamme ei salvestata.",
|
||||
"practiceGrammar": "Harjuta grammatikat",
|
||||
"notEnoughToPractice": "Saada rohkem sõnumeid, et harjutust avada",
|
||||
"constructUseCorGCDesc": "Õige grammatika kategooria harjutus",
|
||||
"constructUseIncGCDesc": "Vale grammatika kategooria harjutus",
|
||||
"@exitPractice": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"@practiceGrammar": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"@notEnoughToPractice": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"@constructUseCorGCDesc": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"@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": {}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"@@locale": "eu",
|
||||
"@@last_modified": "2026-01-13 15:16:47.445226",
|
||||
"@@last_modified": "2026-01-20 12:30:40.144354",
|
||||
"about": "Honi buruz",
|
||||
"@about": {
|
||||
"type": "String",
|
||||
|
|
@ -10840,5 +10840,55 @@
|
|||
"@webDownloadPermissionMessage": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"exitPractice": "Zure praktika saioaren aurrerapena ez da gorde.",
|
||||
"practiceGrammar": "Gramatika praktikatu",
|
||||
"notEnoughToPractice": "Praktika desblokeatzeko gehiago mezu bidali",
|
||||
"constructUseCorGCDesc": "Gramatika kategoriako praktika zuzena",
|
||||
"constructUseIncGCDesc": "Gramatika kategoriako praktika okerra",
|
||||
"@exitPractice": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"@practiceGrammar": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"@notEnoughToPractice": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"@constructUseCorGCDesc": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"@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": {}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
{
|
||||
"@@last_modified": "2026-01-13 15:17:33.639092",
|
||||
"@@last_modified": "2026-01-20 12:31:52.231221",
|
||||
"repeatPassword": "تکرار رمزعبور",
|
||||
"@repeatPassword": {},
|
||||
"about": "درباره",
|
||||
|
|
@ -11572,5 +11572,55 @@
|
|||
"@webDownloadPermissionMessage": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"exitPractice": "پیشرفت جلسه تمرین شما ذخیره نخواهد شد.",
|
||||
"practiceGrammar": "تمرین گرامر",
|
||||
"notEnoughToPractice": "پیامهای بیشتری ارسال کنید تا تمرین را باز کنید",
|
||||
"constructUseCorGCDesc": "تمرین دسته گرامر صحیح",
|
||||
"constructUseIncGCDesc": "تمرین دسته گرامر نادرست",
|
||||
"@exitPractice": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"@practiceGrammar": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"@notEnoughToPractice": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"@constructUseCorGCDesc": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"@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": {}
|
||||
}
|
||||
}
|
||||
|
|
@ -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-13 15:16:29.232516",
|
||||
"@@last_modified": "2026-01-20 12:30:08.099637",
|
||||
"@alwaysUse24HourFormat": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
|
|
@ -11463,5 +11463,55 @@
|
|||
"@webDownloadPermissionMessage": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"exitPractice": "Harjoitussession edistystäsi ei tallenneta.",
|
||||
"practiceGrammar": "Harjoittele kielioppia",
|
||||
"notEnoughToPractice": "Lähetä lisää viestejä avataksesi harjoituksen",
|
||||
"constructUseCorGCDesc": "Oikean kielioppikategorian harjoittelu",
|
||||
"constructUseIncGCDesc": "Väärän kielioppikategorian harjoittelu",
|
||||
"@exitPractice": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"@practiceGrammar": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"@notEnoughToPractice": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"@constructUseCorGCDesc": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"@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": {}
|
||||
}
|
||||
}
|
||||
|
|
@ -2787,7 +2787,7 @@
|
|||
"selectAll": "Piliin lahat",
|
||||
"deselectAll": "Huwag piliin lahat",
|
||||
"@@locale": "fil",
|
||||
"@@last_modified": "2026-01-13 15:17:14.804213",
|
||||
"@@last_modified": "2026-01-20 12:31:19.884620",
|
||||
"@setCustomPermissionLevel": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
|
|
@ -11816,5 +11816,55 @@
|
|||
"@webDownloadPermissionMessage": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"exitPractice": "Hindi mase-save ang iyong progreso sa sesyon ng pagsasanay.",
|
||||
"practiceGrammar": "Magsanay ng gramatika",
|
||||
"notEnoughToPractice": "Magpadala ng higit pang mga mensahe upang i-unlock ang pagsasanay",
|
||||
"constructUseCorGCDesc": "Pagsasanay sa tamang kategorya ng gramatika",
|
||||
"constructUseIncGCDesc": "Pagsasanay sa maling kategorya ng gramatika",
|
||||
"@exitPractice": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"@practiceGrammar": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"@notEnoughToPractice": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"@constructUseCorGCDesc": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"@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": {}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"@@locale": "fr",
|
||||
"@@last_modified": "2026-01-13 15:17:52.389568",
|
||||
"@@last_modified": "2026-01-20 12:32:20.398562",
|
||||
"about": "À propos",
|
||||
"@about": {
|
||||
"type": "String",
|
||||
|
|
@ -11164,5 +11164,55 @@
|
|||
"@webDownloadPermissionMessage": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"exitPractice": "Les progrès de votre session de pratique ne seront pas enregistrés.",
|
||||
"practiceGrammar": "Pratiquer la grammaire",
|
||||
"notEnoughToPractice": "Envoyez plus de messages pour débloquer la pratique",
|
||||
"constructUseCorGCDesc": "Pratique de la catégorie de grammaire correcte",
|
||||
"constructUseIncGCDesc": "Pratique de la catégorie de grammaire incorrecte",
|
||||
"@exitPractice": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"@practiceGrammar": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"@notEnoughToPractice": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"@constructUseCorGCDesc": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"@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": {}
|
||||
}
|
||||
}
|
||||
|
|
@ -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-13 15:17:50.751286",
|
||||
"@@last_modified": "2026-01-20 12:32:18.033824",
|
||||
"@customReaction": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
|
|
@ -10838,5 +10838,55 @@
|
|||
"@webDownloadPermissionMessage": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"exitPractice": "Ní shábhálfar do dhul chun cinn sa seisiún cleachtaidh.",
|
||||
"practiceGrammar": "Cleachtaigh gramadach",
|
||||
"notEnoughToPractice": "Seol níos mó teachtaireachtaí chun cleachtadh a dhíghlasáil",
|
||||
"constructUseCorGCDesc": "Cleachtadh catagóir gramadaí ceart",
|
||||
"constructUseIncGCDesc": "Cleachtadh catagóir gramadaí mícheart",
|
||||
"@exitPractice": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"@practiceGrammar": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"@notEnoughToPractice": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"@constructUseCorGCDesc": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"@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": {}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"@@locale": "gl",
|
||||
"@@last_modified": "2026-01-13 15:16:27.098717",
|
||||
"@@last_modified": "2026-01-20 12:30:05.234280",
|
||||
"about": "Acerca de",
|
||||
"@about": {
|
||||
"type": "String",
|
||||
|
|
@ -10837,5 +10837,55 @@
|
|||
"@webDownloadPermissionMessage": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"exitPractice": "O progreso da túa sesión de práctica non se gardará.",
|
||||
"practiceGrammar": "Practicar gramática",
|
||||
"notEnoughToPractice": "Envía máis mensaxes para desbloquear a práctica",
|
||||
"constructUseCorGCDesc": "Práctica da categoría de gramática correcta",
|
||||
"constructUseIncGCDesc": "Práctica da categoría de gramática incorrecta",
|
||||
"@exitPractice": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"@practiceGrammar": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"@notEnoughToPractice": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"@constructUseCorGCDesc": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"@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": {}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
{
|
||||
"@@last_modified": "2026-01-13 15:16:42.956612",
|
||||
"@@last_modified": "2026-01-20 12:30:31.884050",
|
||||
"about": "אודות",
|
||||
"@about": {
|
||||
"type": "String",
|
||||
|
|
@ -11889,5 +11889,55 @@
|
|||
"@webDownloadPermissionMessage": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"exitPractice": "ההתקדמות שלך במפגש האימון לא תישמר.",
|
||||
"practiceGrammar": "אימון דקדוק",
|
||||
"notEnoughToPractice": "שלח יותר הודעות כדי לפתוח אימון",
|
||||
"constructUseCorGCDesc": "אימון בקטגוריית דקדוק נכון",
|
||||
"constructUseIncGCDesc": "אימון בקטגוריית דקדוק לא נכון",
|
||||
"@exitPractice": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"@practiceGrammar": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"@notEnoughToPractice": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"@constructUseCorGCDesc": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"@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": {}
|
||||
}
|
||||
}
|
||||
|
|
@ -4483,7 +4483,7 @@
|
|||
"playWithAI": "अभी के लिए एआई के साथ खेलें",
|
||||
"courseStartDesc": "पैंजिया बॉट कभी भी जाने के लिए तैयार है!\n\n...लेकिन दोस्तों के साथ सीखना बेहतर है!",
|
||||
"@@locale": "hi",
|
||||
"@@last_modified": "2026-01-13 15:17:42.151930",
|
||||
"@@last_modified": "2026-01-20 12:32:05.240015",
|
||||
"@alwaysUse24HourFormat": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
|
|
@ -11925,5 +11925,55 @@
|
|||
"@webDownloadPermissionMessage": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"exitPractice": "आपकी प्रैक्टिस सत्र की प्रगति सहेजी नहीं जाएगी।",
|
||||
"practiceGrammar": "व्याकरण का अभ्यास करें",
|
||||
"notEnoughToPractice": "अभ्यास अनलॉक करने के लिए अधिक संदेश भेजें",
|
||||
"constructUseCorGCDesc": "सही व्याकरण श्रेणी का अभ्यास",
|
||||
"constructUseIncGCDesc": "गलत व्याकरण श्रेणी का अभ्यास",
|
||||
"@exitPractice": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"@practiceGrammar": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"@notEnoughToPractice": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"@constructUseCorGCDesc": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"@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": {}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"@@locale": "hr",
|
||||
"@@last_modified": "2026-01-13 15:16:41.523925",
|
||||
"@@last_modified": "2026-01-20 12:30:29.823787",
|
||||
"about": "Informacije",
|
||||
"@about": {
|
||||
"type": "String",
|
||||
|
|
@ -11212,5 +11212,55 @@
|
|||
"@webDownloadPermissionMessage": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"exitPractice": "Vaš napredak u vježbi neće biti spremljen.",
|
||||
"practiceGrammar": "Vježbajte gramatiku",
|
||||
"notEnoughToPractice": "Pošaljite više poruka da otključate vježbu",
|
||||
"constructUseCorGCDesc": "Vježba ispravne gramatičke kategorije",
|
||||
"constructUseIncGCDesc": "Vježba neispravne gramatičke kategorije",
|
||||
"@exitPractice": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"@practiceGrammar": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"@notEnoughToPractice": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"@constructUseCorGCDesc": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"@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": {}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"@@locale": "hu",
|
||||
"@@last_modified": "2026-01-13 15:16:32.515672",
|
||||
"@@last_modified": "2026-01-20 12:30:13.762355",
|
||||
"about": "Névjegy",
|
||||
"@about": {
|
||||
"type": "String",
|
||||
|
|
@ -10841,5 +10841,55 @@
|
|||
"@webDownloadPermissionMessage": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"exitPractice": "A gyakorlási session előrehaladása nem lesz mentve.",
|
||||
"practiceGrammar": "Nyelvtan gyakorlása",
|
||||
"notEnoughToPractice": "Több üzenetet kell küldeni a gyakorlás feloldásához",
|
||||
"constructUseCorGCDesc": "Helyes nyelvtani kategória gyakorlása",
|
||||
"constructUseIncGCDesc": "Helytelen nyelvtani kategória gyakorlása",
|
||||
"@exitPractice": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"@practiceGrammar": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"@notEnoughToPractice": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"@constructUseCorGCDesc": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"@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": {}
|
||||
}
|
||||
}
|
||||
|
|
@ -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-13 15:16:44.800788",
|
||||
"@@last_modified": "2026-01-20 12:30:35.012898",
|
||||
"@alwaysUse24HourFormat": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
|
|
@ -11918,5 +11918,55 @@
|
|||
"@webDownloadPermissionMessage": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"exitPractice": "Progresul sesiunii tale de practică nu va fi salvat.",
|
||||
"practiceGrammar": "Exersează gramatică",
|
||||
"notEnoughToPractice": "Trimite mai multe mesaje pentru a debloca practica",
|
||||
"constructUseCorGCDesc": "Practică categoria de gramatică corectă",
|
||||
"constructUseIncGCDesc": "Practică categoria de gramatică incorectă",
|
||||
"@exitPractice": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"@practiceGrammar": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"@notEnoughToPractice": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"@constructUseCorGCDesc": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"@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": {}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
{
|
||||
"@@last_modified": "2026-01-13 15:16:34.707887",
|
||||
"@@last_modified": "2026-01-20 12:30:15.788809",
|
||||
"setAsCanonicalAlias": "Atur sebagai alias utama",
|
||||
"@setAsCanonicalAlias": {
|
||||
"type": "String",
|
||||
|
|
@ -10831,5 +10831,55 @@
|
|||
"@webDownloadPermissionMessage": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"exitPractice": "Kemajuan sesi latihan Anda tidak akan disimpan.",
|
||||
"practiceGrammar": "Latihan tata bahasa",
|
||||
"notEnoughToPractice": "Kirim lebih banyak pesan untuk membuka latihan",
|
||||
"constructUseCorGCDesc": "Latihan kategori tata bahasa yang benar",
|
||||
"constructUseIncGCDesc": "Latihan kategori tata bahasa yang salah",
|
||||
"@exitPractice": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"@practiceGrammar": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"@notEnoughToPractice": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"@constructUseCorGCDesc": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"@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": {}
|
||||
}
|
||||
}
|
||||
|
|
@ -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-13 15:16:39.872561",
|
||||
"@@last_modified": "2026-01-20 12:30:26.700297",
|
||||
"@alwaysUse24HourFormat": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
|
|
@ -11814,5 +11814,55 @@
|
|||
"@webDownloadPermissionMessage": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"exitPractice": "Tua progressio sessionis exercitationis non servabitur.",
|
||||
"practiceGrammar": "Exercitia grammatica",
|
||||
"notEnoughToPractice": "Mitte plura nuntia ad exercitium aperiendum",
|
||||
"constructUseCorGCDesc": "Correcta grammaticae categoriae exercitium",
|
||||
"constructUseIncGCDesc": "Incorrecta grammaticae categoriae exercitium",
|
||||
"@exitPractice": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"@practiceGrammar": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"@notEnoughToPractice": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"@constructUseCorGCDesc": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"@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": {}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
{
|
||||
"@@last_modified": "2026-01-13 15:16:57.804466",
|
||||
"@@last_modified": "2026-01-20 12:30:55.791406",
|
||||
"about": "Informazioni",
|
||||
"@about": {
|
||||
"type": "String",
|
||||
|
|
@ -10843,5 +10843,55 @@
|
|||
"@webDownloadPermissionMessage": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"exitPractice": "I progressi della tua sessione di pratica non verranno salvati.",
|
||||
"practiceGrammar": "Pratica la grammatica",
|
||||
"notEnoughToPractice": "Invia più messaggi per sbloccare la pratica",
|
||||
"constructUseCorGCDesc": "Pratica della categoria grammaticale corretta",
|
||||
"constructUseIncGCDesc": "Pratica della categoria grammaticale scorretta",
|
||||
"@exitPractice": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"@practiceGrammar": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"@notEnoughToPractice": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"@constructUseCorGCDesc": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"@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": {}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"@@locale": "ja",
|
||||
"@@last_modified": "2026-01-13 15:17:40.716657",
|
||||
"@@last_modified": "2026-01-20 12:32:02.883097",
|
||||
"about": "このアプリについて",
|
||||
"@about": {
|
||||
"type": "String",
|
||||
|
|
@ -11630,5 +11630,55 @@
|
|||
"@webDownloadPermissionMessage": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"exitPractice": "あなたの練習セッションの進捗は保存されません。",
|
||||
"practiceGrammar": "文法を練習する",
|
||||
"notEnoughToPractice": "練習を解除するにはもっとメッセージを送信してください",
|
||||
"constructUseCorGCDesc": "正しい文法カテゴリの練習",
|
||||
"constructUseIncGCDesc": "間違った文法カテゴリの練習",
|
||||
"@exitPractice": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"@practiceGrammar": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"@notEnoughToPractice": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"@constructUseCorGCDesc": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"@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": {}
|
||||
}
|
||||
}
|
||||
|
|
@ -2594,7 +2594,7 @@
|
|||
"playWithAI": "ამ დროისთვის ითამაშეთ AI-თან",
|
||||
"courseStartDesc": "Pangea Bot მზადაა ნებისმიერ დროს გასასვლელად!\n\n...მაგრამ სწავლა უკეთესია მეგობრებთან ერთად!",
|
||||
"@@locale": "ka",
|
||||
"@@last_modified": "2026-01-13 15:17:47.132620",
|
||||
"@@last_modified": "2026-01-20 12:32:12.611848",
|
||||
"@alwaysUse24HourFormat": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
|
|
@ -11870,5 +11870,55 @@
|
|||
"@webDownloadPermissionMessage": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"exitPractice": "თქვენი პრაქტიკის სესიის პროგრესი არ დაიშლება.",
|
||||
"practiceGrammar": "პრაქტიკა გრამატიკა",
|
||||
"notEnoughToPractice": "პრაქტიკის გასახსნელად მეტი შეტყობინება გამოაგზავნეთ",
|
||||
"constructUseCorGCDesc": "სწორი გრამატიკული კატეგორიის პრაქტიკა",
|
||||
"constructUseIncGCDesc": "არასწორი გრამატიკული კატეგორიის პრაქტიკა",
|
||||
"@exitPractice": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"@practiceGrammar": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"@notEnoughToPractice": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"@constructUseCorGCDesc": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"@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": {}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
{
|
||||
"@@last_modified": "2026-01-13 15:16:23.091865",
|
||||
"@@last_modified": "2026-01-20 12:29:56.840054",
|
||||
"about": "소개",
|
||||
"@about": {
|
||||
"type": "String",
|
||||
|
|
@ -10948,5 +10948,55 @@
|
|||
"@webDownloadPermissionMessage": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"exitPractice": "연습 세션 진행 상황이 저장되지 않습니다.",
|
||||
"practiceGrammar": "문법 연습",
|
||||
"notEnoughToPractice": "연습을 잠금 해제하려면 더 많은 메시지를 보내세요.",
|
||||
"constructUseCorGCDesc": "올바른 문법 카테고리 연습",
|
||||
"constructUseIncGCDesc": "잘못된 문법 카테고리 연습",
|
||||
"@exitPractice": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"@practiceGrammar": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"@notEnoughToPractice": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"@constructUseCorGCDesc": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"@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": {}
|
||||
}
|
||||
}
|
||||
|
|
@ -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-13 15:17:23.202279",
|
||||
"@@last_modified": "2026-01-20 12:31:36.164653",
|
||||
"@alwaysUse24HourFormat": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
|
|
@ -11645,5 +11645,55 @@
|
|||
"@webDownloadPermissionMessage": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"exitPractice": "Jūsų praktikos sesijos pažanga nebus išsaugota.",
|
||||
"practiceGrammar": "Praktikuoti gramatiką",
|
||||
"notEnoughToPractice": "Siųskite daugiau žinučių, kad atrakintumėte praktiką",
|
||||
"constructUseCorGCDesc": "Teisingos gramatikos kategorijos praktika",
|
||||
"constructUseIncGCDesc": "Neteisingos gramatikos kategorijos praktika",
|
||||
"@exitPractice": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"@practiceGrammar": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"@notEnoughToPractice": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"@constructUseCorGCDesc": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"@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": {}
|
||||
}
|
||||
}
|
||||
|
|
@ -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-13 15:17:16.858338",
|
||||
"@@last_modified": "2026-01-20 12:31:22.453166",
|
||||
"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",
|
||||
|
|
@ -10826,5 +10826,55 @@
|
|||
"@webDownloadPermissionMessage": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"exitPractice": "Jūsu prakses sesijas progress netiks saglabāts.",
|
||||
"practiceGrammar": "Praktizēt gramatiku",
|
||||
"notEnoughToPractice": "Sūtiet vairāk ziņojumu, lai atbloķētu praksi",
|
||||
"constructUseCorGCDesc": "Pareizas gramatikas kategorijas prakse",
|
||||
"constructUseIncGCDesc": "Nepareizas gramatikas kategorijas prakse",
|
||||
"@exitPractice": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"@practiceGrammar": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"@notEnoughToPractice": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"@constructUseCorGCDesc": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"@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": {}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
{
|
||||
"@@last_modified": "2026-01-13 15:17:04.771433",
|
||||
"@@last_modified": "2026-01-20 12:31:02.821968",
|
||||
"about": "Om",
|
||||
"@about": {
|
||||
"type": "String",
|
||||
|
|
@ -11933,5 +11933,55 @@
|
|||
"@webDownloadPermissionMessage": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"exitPractice": "Fremdriften i økten din vil ikke bli lagret.",
|
||||
"practiceGrammar": "Øv på grammatikk",
|
||||
"notEnoughToPractice": "Send flere meldinger for å låse opp øving",
|
||||
"constructUseCorGCDesc": "Øvelse i korrekt grammatikkategori",
|
||||
"constructUseIncGCDesc": "Øvelse i ukorrekt grammatikkategori",
|
||||
"@exitPractice": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"@practiceGrammar": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"@notEnoughToPractice": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"@constructUseCorGCDesc": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"@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": {}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
{
|
||||
"@@last_modified": "2026-01-13 15:17:28.318296",
|
||||
"@@last_modified": "2026-01-20 12:31:42.507524",
|
||||
"about": "Over ons",
|
||||
"@about": {
|
||||
"type": "String",
|
||||
|
|
@ -10840,5 +10840,55 @@
|
|||
"@webDownloadPermissionMessage": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"exitPractice": "Uw voortgang in de oefensessie wordt niet opgeslagen.",
|
||||
"practiceGrammar": "Oefen grammatica",
|
||||
"notEnoughToPractice": "Stuur meer berichten om de oefening te ontgrendelen",
|
||||
"constructUseCorGCDesc": "Oefening in de juiste grammaticacategorie",
|
||||
"constructUseIncGCDesc": "Oefening in de onjuiste grammaticacategorie",
|
||||
"@exitPractice": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"@practiceGrammar": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"@notEnoughToPractice": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"@constructUseCorGCDesc": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"@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": {}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"@@locale": "pl",
|
||||
"@@last_modified": "2026-01-13 15:17:35.488276",
|
||||
"@@last_modified": "2026-01-20 12:31:54.796841",
|
||||
"about": "O aplikacji",
|
||||
"@about": {
|
||||
"type": "String",
|
||||
|
|
@ -10838,5 +10838,55 @@
|
|||
"@webDownloadPermissionMessage": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"exitPractice": "Postęp Twojej sesji ćwiczeń nie zostanie zapisany.",
|
||||
"practiceGrammar": "Ćwicz gramatykę",
|
||||
"notEnoughToPractice": "Wyślij więcej wiadomości, aby odblokować ćwiczenia",
|
||||
"constructUseCorGCDesc": "Ćwiczenie poprawnej kategorii gramatycznej",
|
||||
"constructUseIncGCDesc": "Ćwiczenie niepoprawnej kategorii gramatycznej",
|
||||
"@exitPractice": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"@practiceGrammar": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"@notEnoughToPractice": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"@constructUseCorGCDesc": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"@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": {}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
{
|
||||
"@@last_modified": "2026-01-13 15:16:49.002664",
|
||||
"@@last_modified": "2026-01-20 12:30:42.601068",
|
||||
"copiedToClipboard": "Copiada para a área de transferência",
|
||||
"@copiedToClipboard": {
|
||||
"type": "String",
|
||||
|
|
@ -11940,5 +11940,55 @@
|
|||
"@webDownloadPermissionMessage": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"exitPractice": "O progresso da sua sessão de prática não será salvo.",
|
||||
"practiceGrammar": "Praticar gramática",
|
||||
"notEnoughToPractice": "Envie mais mensagens para desbloquear a prática",
|
||||
"constructUseCorGCDesc": "Prática da categoria de gramática correta",
|
||||
"constructUseIncGCDesc": "Prática da categoria de gramática incorreta",
|
||||
"@exitPractice": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"@practiceGrammar": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"@notEnoughToPractice": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"@constructUseCorGCDesc": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"@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": {}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
{
|
||||
"@@last_modified": "2026-01-13 15:16:45.933049",
|
||||
"@@last_modified": "2026-01-20 12:30:37.380939",
|
||||
"about": "Sobre",
|
||||
"@about": {
|
||||
"type": "String",
|
||||
|
|
@ -11198,5 +11198,55 @@
|
|||
"@webDownloadPermissionMessage": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"exitPractice": "O progresso da sua sessão de prática não será salvo.",
|
||||
"practiceGrammar": "Praticar gramática",
|
||||
"notEnoughToPractice": "Envie mais mensagens para desbloquear a prática",
|
||||
"constructUseCorGCDesc": "Prática da categoria de gramática correta",
|
||||
"constructUseIncGCDesc": "Prática da categoria de gramática incorreta",
|
||||
"@exitPractice": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"@practiceGrammar": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"@notEnoughToPractice": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"@constructUseCorGCDesc": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"@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": {}
|
||||
}
|
||||
}
|
||||
|
|
@ -3331,7 +3331,7 @@
|
|||
"selectAll": "Selecionar tudo",
|
||||
"deselectAll": "Desmarcar tudo",
|
||||
"@@locale": "pt_PT",
|
||||
"@@last_modified": "2026-01-13 15:17:11.533911",
|
||||
"@@last_modified": "2026-01-20 12:31:13.609297",
|
||||
"@alwaysUse24HourFormat": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
|
|
@ -11869,5 +11869,55 @@
|
|||
"@webDownloadPermissionMessage": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"exitPractice": "O progresso da sua sessão de prática não será salvo.",
|
||||
"practiceGrammar": "Praticar gramática",
|
||||
"notEnoughToPractice": "Envie mais mensagens para desbloquear a prática",
|
||||
"constructUseCorGCDesc": "Prática da categoria de gramática correta",
|
||||
"constructUseIncGCDesc": "Prática da categoria de gramática incorreta",
|
||||
"@exitPractice": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"@practiceGrammar": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"@notEnoughToPractice": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"@constructUseCorGCDesc": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"@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": {}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
{
|
||||
"@@last_modified": "2026-01-13 15:16:36.323428",
|
||||
"@@last_modified": "2026-01-20 12:30:21.408747",
|
||||
"about": "Despre",
|
||||
"@about": {
|
||||
"type": "String",
|
||||
|
|
@ -11575,5 +11575,55 @@
|
|||
"@webDownloadPermissionMessage": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"exitPractice": "Progresul sesiunii tale de practică nu va fi salvat.",
|
||||
"practiceGrammar": "Exersează gramatică",
|
||||
"notEnoughToPractice": "Trimite mai multe mesaje pentru a debloca practica",
|
||||
"constructUseCorGCDesc": "Practică categoria de gramatică corectă",
|
||||
"constructUseIncGCDesc": "Practică categoria de gramatică incorectă",
|
||||
"@exitPractice": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"@practiceGrammar": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"@notEnoughToPractice": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"@constructUseCorGCDesc": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"@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": {}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"@@locale": "ru",
|
||||
"@@last_modified": "2026-01-13 15:17:45.324896",
|
||||
"@@last_modified": "2026-01-20 12:32:09.850624",
|
||||
"about": "О проекте",
|
||||
"@about": {
|
||||
"type": "String",
|
||||
|
|
@ -10945,5 +10945,55 @@
|
|||
"@webDownloadPermissionMessage": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"exitPractice": "Ваш прогресс в сессии практики не будет сохранен.",
|
||||
"practiceGrammar": "Практика грамматики",
|
||||
"notEnoughToPractice": "Отправьте больше сообщений, чтобы разблокировать практику",
|
||||
"constructUseCorGCDesc": "Практика правильной грамматики",
|
||||
"constructUseIncGCDesc": "Практика неправильной грамматики",
|
||||
"@exitPractice": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"@practiceGrammar": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"@notEnoughToPractice": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"@constructUseCorGCDesc": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"@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": {}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"@@locale": "sk",
|
||||
"@@last_modified": "2026-01-13 15:16:38.044008",
|
||||
"@@last_modified": "2026-01-20 12:30:24.498564",
|
||||
"about": "O aplikácii",
|
||||
"@about": {
|
||||
"type": "String",
|
||||
|
|
@ -11924,5 +11924,55 @@
|
|||
"@webDownloadPermissionMessage": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"exitPractice": "Pokrok vo vašej cvičebnej relácii nebude uložený.",
|
||||
"practiceGrammar": "Cvičiť gramatiku",
|
||||
"notEnoughToPractice": "Odošlite viac správ na odomknutie cvičenia",
|
||||
"constructUseCorGCDesc": "Cvičenie správnej gramatickej kategórie",
|
||||
"constructUseIncGCDesc": "Cvičenie nesprávnej gramatickej kategórie",
|
||||
"@exitPractice": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"@practiceGrammar": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"@notEnoughToPractice": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"@constructUseCorGCDesc": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"@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": {}
|
||||
}
|
||||
}
|
||||
|
|
@ -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-13 15:16:54.017311",
|
||||
"@@last_modified": "2026-01-20 12:30:51.399294",
|
||||
"@alwaysUse24HourFormat": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
|
|
@ -11921,5 +11921,55 @@
|
|||
"@webDownloadPermissionMessage": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"exitPractice": "Napredek vaše seje vadbe ne bo shranjen.",
|
||||
"practiceGrammar": "Vadite slovnico",
|
||||
"notEnoughToPractice": "Pošljite več sporočil, da odklenete vadbo",
|
||||
"constructUseCorGCDesc": "Vadba pravilne slovnice",
|
||||
"constructUseIncGCDesc": "Vadba nepravilne slovnice",
|
||||
"@exitPractice": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"@practiceGrammar": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"@notEnoughToPractice": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"@constructUseCorGCDesc": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"@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": {}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
{
|
||||
"@@last_modified": "2026-01-13 15:17:49.129767",
|
||||
"@@last_modified": "2026-01-20 12:32:14.799697",
|
||||
"about": "О програму",
|
||||
"@about": {
|
||||
"type": "String",
|
||||
|
|
@ -11942,5 +11942,55 @@
|
|||
"@webDownloadPermissionMessage": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"exitPractice": "Vaš napredak u vežbanju neće biti sačuvan.",
|
||||
"practiceGrammar": "Vežbajte gramatiku",
|
||||
"notEnoughToPractice": "Pošaljite više poruka da otključate vežbanje",
|
||||
"constructUseCorGCDesc": "Vežbanje ispravne gramatike",
|
||||
"constructUseIncGCDesc": "Vežbanje nepravilne gramatike",
|
||||
"@exitPractice": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"@practiceGrammar": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"@notEnoughToPractice": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"@constructUseCorGCDesc": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"@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": {}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
{
|
||||
"@@last_modified": "2026-01-13 15:17:36.926028",
|
||||
"@@last_modified": "2026-01-20 12:31:57.066428",
|
||||
"about": "Om",
|
||||
"@about": {
|
||||
"type": "String",
|
||||
|
|
@ -11318,5 +11318,55 @@
|
|||
"@webDownloadPermissionMessage": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"exitPractice": "Din övningssession kommer inte att sparas.",
|
||||
"practiceGrammar": "Öva grammatik",
|
||||
"notEnoughToPractice": "Skicka fler meddelanden för att låsa upp övning",
|
||||
"constructUseCorGCDesc": "Övning i korrekt grammatikkategori",
|
||||
"constructUseIncGCDesc": "Övning i inkorrekt grammatikkategori",
|
||||
"@exitPractice": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"@practiceGrammar": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"@notEnoughToPractice": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"@constructUseCorGCDesc": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"@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": {}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
{
|
||||
"@@last_modified": "2026-01-13 15:17:27.095421",
|
||||
"@@last_modified": "2026-01-20 12:31:40.562260",
|
||||
"acceptedTheInvitation": "👍 {username} அழைப்பை ஏற்றுக்கொண்டது",
|
||||
"@acceptedTheInvitation": {
|
||||
"type": "String",
|
||||
|
|
@ -11064,5 +11064,55 @@
|
|||
"@webDownloadPermissionMessage": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"exitPractice": "உங்கள் பயிற்சி அமர்வின் முன்னேற்றம் சேமிக்கப்படாது.",
|
||||
"practiceGrammar": "வியாசத்தை பயிற்சி செய்யவும்",
|
||||
"notEnoughToPractice": "பயிற்சியை திறக்க மேலும் செய்திகளை அனுப்பவும்",
|
||||
"constructUseCorGCDesc": "சரியான வியாச வகை பயிற்சி",
|
||||
"constructUseIncGCDesc": "தவறான வியாச வகை பயிற்சி",
|
||||
"@exitPractice": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"@practiceGrammar": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"@notEnoughToPractice": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"@constructUseCorGCDesc": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"@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": {}
|
||||
}
|
||||
}
|
||||
|
|
@ -1920,7 +1920,7 @@
|
|||
"playWithAI": "ఇప్పుడే AI తో ఆడండి",
|
||||
"courseStartDesc": "పాంజియా బాట్ ఎప్పుడైనా సిద్ధంగా ఉంటుంది!\n\n...కానీ స్నేహితులతో నేర్చుకోవడం మెరుగైనది!",
|
||||
"@@locale": "te",
|
||||
"@@last_modified": "2026-01-13 15:17:21.774356",
|
||||
"@@last_modified": "2026-01-20 12:31:32.903548",
|
||||
"@setCustomPermissionLevel": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
|
|
@ -11929,5 +11929,55 @@
|
|||
"@webDownloadPermissionMessage": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"exitPractice": "మీ ప్రాక్టీస్ సెషన్ పురోగతి సేవ్ చేయబడదు.",
|
||||
"practiceGrammar": "వ్యాకరణాన్ని అభ్యాసం చేయండి",
|
||||
"notEnoughToPractice": "ప్రాక్టీస్ను అన్లాక్ చేయడానికి మరింత సందేశాలు పంపండి",
|
||||
"constructUseCorGCDesc": "సరైన వ్యాకరణ శ్రేణి ప్రాక్టీస్",
|
||||
"constructUseIncGCDesc": "తప్పు వ్యాకరణ శ్రేణి ప్రాక్టీస్",
|
||||
"@exitPractice": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"@practiceGrammar": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"@notEnoughToPractice": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"@constructUseCorGCDesc": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"@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": {}
|
||||
}
|
||||
}
|
||||
|
|
@ -4456,7 +4456,7 @@
|
|||
"playWithAI": "เล่นกับ AI ชั่วคราว",
|
||||
"courseStartDesc": "Pangea Bot พร้อมที่จะเริ่มต้นได้ทุกเมื่อ!\n\n...แต่การเรียนรู้ดีกว่ากับเพื่อน!",
|
||||
"@@locale": "th",
|
||||
"@@last_modified": "2026-01-13 15:17:10.203231",
|
||||
"@@last_modified": "2026-01-20 12:31:11.891533",
|
||||
"@alwaysUse24HourFormat": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
|
|
@ -11898,5 +11898,55 @@
|
|||
"@webDownloadPermissionMessage": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"exitPractice": "ความก้าวหน้าของการฝึกฝนของคุณจะไม่ถูกบันทึก",
|
||||
"practiceGrammar": "ฝึกไวยากรณ์",
|
||||
"notEnoughToPractice": "ส่งข้อความเพิ่มเติมเพื่อปลดล็อกการฝึกฝน",
|
||||
"constructUseCorGCDesc": "การฝึกไวยากรณ์หมวดหมู่ที่ถูกต้อง",
|
||||
"constructUseIncGCDesc": "การฝึกไวยากรณ์หมวดหมู่ที่ไม่ถูกต้อง",
|
||||
"@exitPractice": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"@practiceGrammar": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"@notEnoughToPractice": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"@constructUseCorGCDesc": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"@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": {}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"@@locale": "tr",
|
||||
"@@last_modified": "2026-01-13 15:17:19.618708",
|
||||
"@@last_modified": "2026-01-20 12:31:28.469826",
|
||||
"about": "Hakkında",
|
||||
"@about": {
|
||||
"type": "String",
|
||||
|
|
@ -11062,5 +11062,55 @@
|
|||
"@webDownloadPermissionMessage": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"exitPractice": "Pratik oturumunuzun ilerlemesi kaydedilmeyecek.",
|
||||
"practiceGrammar": "Dil bilgisi pratiği yap",
|
||||
"notEnoughToPractice": "Pratik yapmak için daha fazla mesaj gönderin",
|
||||
"constructUseCorGCDesc": "Doğru dil bilgisi kategorisi pratiği",
|
||||
"constructUseIncGCDesc": "Yanlış dil bilgisi kategorisi pratiği",
|
||||
"@exitPractice": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"@practiceGrammar": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"@notEnoughToPractice": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"@constructUseCorGCDesc": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"@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": {}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"@@locale": "uk",
|
||||
"@@last_modified": "2026-01-13 15:17:01.594985",
|
||||
"@@last_modified": "2026-01-20 12:30:57.869011",
|
||||
"about": "Про застосунок",
|
||||
"@about": {
|
||||
"type": "String",
|
||||
|
|
@ -10834,5 +10834,55 @@
|
|||
"@webDownloadPermissionMessage": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"exitPractice": "Ваш прогрес у сесії практики не буде збережено.",
|
||||
"practiceGrammar": "Практика граматики",
|
||||
"notEnoughToPractice": "Надішліть більше повідомлень, щоб розблокувати практику",
|
||||
"constructUseCorGCDesc": "Практика правильної граматичної категорії",
|
||||
"constructUseIncGCDesc": "Практика неправильної граматичної категорії",
|
||||
"@exitPractice": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"@practiceGrammar": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"@notEnoughToPractice": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"@constructUseCorGCDesc": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"@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": {}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
{
|
||||
"@@last_modified": "2026-01-13 15:17:24.923117",
|
||||
"@@last_modified": "2026-01-20 12:31:38.101874",
|
||||
"about": "Giới thiệu",
|
||||
"@about": {
|
||||
"type": "String",
|
||||
|
|
@ -6410,5 +6410,55 @@
|
|||
"@webDownloadPermissionMessage": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"exitPractice": "Tiến trình phiên thực hành của bạn sẽ không được lưu.",
|
||||
"practiceGrammar": "Thực hành ngữ pháp",
|
||||
"notEnoughToPractice": "Gửi thêm tin nhắn để mở khóa thực hành",
|
||||
"constructUseCorGCDesc": "Thực hành thể loại ngữ pháp đúng",
|
||||
"constructUseIncGCDesc": "Thực hành thể loại ngữ pháp sai",
|
||||
"@exitPractice": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"@practiceGrammar": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"@notEnoughToPractice": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"@constructUseCorGCDesc": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"@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": {}
|
||||
}
|
||||
}
|
||||
|
|
@ -1856,7 +1856,7 @@
|
|||
"selectAll": "全選",
|
||||
"deselectAll": "取消全選",
|
||||
"@@locale": "yue",
|
||||
"@@last_modified": "2026-01-13 15:16:56.151795",
|
||||
"@@last_modified": "2026-01-20 12:30:53.854617",
|
||||
"@ignoreUser": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
|
|
@ -11931,5 +11931,55 @@
|
|||
"@webDownloadPermissionMessage": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"exitPractice": "您的練習進度將不會被保存。",
|
||||
"practiceGrammar": "練習語法",
|
||||
"notEnoughToPractice": "發送更多消息以解鎖練習",
|
||||
"constructUseCorGCDesc": "正確語法類別練習",
|
||||
"constructUseIncGCDesc": "不正確語法類別練習",
|
||||
"@exitPractice": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"@practiceGrammar": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"@notEnoughToPractice": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"@constructUseCorGCDesc": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"@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": {}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"@@locale": "zh",
|
||||
"@@last_modified": "2026-01-13 15:17:30.643003",
|
||||
"@@last_modified": "2026-01-20 12:31:47.017242",
|
||||
"about": "关于",
|
||||
"@about": {
|
||||
"type": "String",
|
||||
|
|
@ -10831,5 +10831,55 @@
|
|||
"@webDownloadPermissionMessage": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"exitPractice": "您的练习会话进度将不会被保存。",
|
||||
"practiceGrammar": "练习语法",
|
||||
"notEnoughToPractice": "发送更多消息以解锁练习",
|
||||
"constructUseCorGCDesc": "正确语法类别练习",
|
||||
"constructUseIncGCDesc": "错误语法类别练习",
|
||||
"@exitPractice": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"@practiceGrammar": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"@notEnoughToPractice": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"@constructUseCorGCDesc": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"@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": {}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
{
|
||||
"@@last_modified": "2026-01-13 15:17:12.846069",
|
||||
"@@last_modified": "2026-01-20 12:31:16.140892",
|
||||
"about": "關於",
|
||||
"@about": {
|
||||
"type": "String",
|
||||
|
|
@ -10838,5 +10838,55 @@
|
|||
"@webDownloadPermissionMessage": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"exitPractice": "您的練習進度將不會被保存。",
|
||||
"practiceGrammar": "練習文法",
|
||||
"notEnoughToPractice": "發送更多訊息以解鎖練習",
|
||||
"constructUseCorGCDesc": "正確文法類別練習",
|
||||
"constructUseIncGCDesc": "不正確文法類別練習",
|
||||
"@exitPractice": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"@practiceGrammar": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"@notEnoughToPractice": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"@constructUseCorGCDesc": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"@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": {}
|
||||
}
|
||||
}
|
||||
|
|
@ -32,7 +32,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';
|
||||
|
|
@ -2046,31 +2045,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,
|
||||
|
|
@ -2263,7 +2237,7 @@ class ChatController extends State<ChatPageWithRoom>
|
|||
bool autosend = false,
|
||||
}) async {
|
||||
if (shouldShowLanguageMismatchPopupByActivity) {
|
||||
return showLanguageMismatchPopup();
|
||||
return showLanguageMismatchPopup(manual: manual);
|
||||
}
|
||||
|
||||
await choreographer.requestWritingAssistance(manual: manual);
|
||||
|
|
@ -2276,7 +2250,7 @@ class ChatController extends State<ChatPageWithRoom>
|
|||
}
|
||||
}
|
||||
|
||||
void showLanguageMismatchPopup() {
|
||||
void showLanguageMismatchPopup({bool manual = false}) {
|
||||
if (!shouldShowLanguageMismatchPopupByActivity) {
|
||||
return;
|
||||
}
|
||||
|
|
@ -2289,11 +2263,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();
|
||||
|
|
@ -2414,6 +2418,8 @@ class ChatController extends State<ChatPageWithRoom>
|
|||
);
|
||||
|
||||
if (reason == null) return;
|
||||
|
||||
clearSelectedEvents();
|
||||
await showFutureLoadingDialog(
|
||||
context: context,
|
||||
future: () => room.sendEvent(
|
||||
|
|
|
|||
|
|
@ -417,7 +417,9 @@ class HtmlMessage extends StatelessWidget {
|
|||
|
||||
final renderer = TokenRenderingUtil();
|
||||
|
||||
final underlineColor = Theme.of(context).colorScheme.primary.withAlpha(200);
|
||||
final underlineColor = pangeaMessageEvent!.ownMessage
|
||||
? ThemeData.dark().colorScheme.primaryContainer.withAlpha(200)
|
||||
: Theme.of(context).colorScheme.primary.withAlpha(200);
|
||||
|
||||
final newTokens =
|
||||
pangeaMessageEvent != null && !pangeaMessageEvent!.ownMessage
|
||||
|
|
|
|||
|
|
@ -16,7 +16,6 @@ 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';
|
||||
|
|
@ -822,20 +821,7 @@ class Message extends StatelessWidget {
|
|||
),
|
||||
],
|
||||
),
|
||||
)
|
||||
// #Pangea
|
||||
else if (canRefresh)
|
||||
RequestRegenerationButton(
|
||||
textColor:
|
||||
textColor,
|
||||
onPressed: () =>
|
||||
controller
|
||||
.requestRegeneration(
|
||||
event
|
||||
.eventId,
|
||||
),
|
||||
),
|
||||
// Pangea#
|
||||
],
|
||||
),
|
||||
),
|
||||
|
|
|
|||
|
|
@ -14,6 +14,7 @@ import 'package:fluffychat/pangea/analytics_data/level_up_analytics_service.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/construct_use_model.dart';
|
||||
import 'package:fluffychat/pangea/analytics_misc/construct_use_type_enum.dart';
|
||||
import 'package:fluffychat/pangea/analytics_misc/constructs_event.dart';
|
||||
import 'package:fluffychat/pangea/analytics_misc/constructs_model.dart';
|
||||
import 'package:fluffychat/pangea/analytics_settings/analytics_settings_extension.dart';
|
||||
|
|
@ -213,6 +214,8 @@ class AnalyticsDataService {
|
|||
await _syncController?.waitForSync(analyticsRoomID);
|
||||
}
|
||||
|
||||
DerivedAnalyticsDataModel? get cachedDerivedData => _cachedDerivedStats;
|
||||
|
||||
Future<DerivedAnalyticsDataModel> get derivedData async {
|
||||
await _ensureInitialized();
|
||||
|
||||
|
|
@ -233,12 +236,14 @@ class AnalyticsDataService {
|
|||
int? count,
|
||||
String? roomId,
|
||||
DateTime? since,
|
||||
ConstructUseTypeEnum? type,
|
||||
}) async {
|
||||
await _ensureInitialized();
|
||||
final uses = await _analyticsClientGetter.database.getUses(
|
||||
count: count,
|
||||
roomId: roomId,
|
||||
since: since,
|
||||
type: type,
|
||||
);
|
||||
|
||||
final blocked = blockedConstructs;
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ import 'package:synchronized/synchronized.dart';
|
|||
import 'package:fluffychat/pangea/analytics_data/derived_analytics_data_model.dart';
|
||||
import 'package:fluffychat/pangea/analytics_misc/construct_type_enum.dart';
|
||||
import 'package:fluffychat/pangea/analytics_misc/construct_use_model.dart';
|
||||
import 'package:fluffychat/pangea/analytics_misc/construct_use_type_enum.dart';
|
||||
import 'package:fluffychat/pangea/analytics_misc/constructs_event.dart';
|
||||
import 'package:fluffychat/pangea/analytics_misc/constructs_model.dart';
|
||||
import 'package:fluffychat/pangea/constructs/construct_identifier.dart';
|
||||
|
|
@ -197,6 +198,7 @@ class AnalyticsDatabase with DatabaseFileStorage {
|
|||
int? count,
|
||||
String? roomId,
|
||||
DateTime? since,
|
||||
ConstructUseTypeEnum? type,
|
||||
}) async {
|
||||
final stopwatch = Stopwatch()..start();
|
||||
final results = <OneConstructUse>[];
|
||||
|
|
@ -208,6 +210,9 @@ class AnalyticsDatabase with DatabaseFileStorage {
|
|||
if (roomId != null && use.metadata.roomId != roomId) {
|
||||
return true; // skip but continue
|
||||
}
|
||||
if (type != null && use.useType != type) {
|
||||
return true; // skip but continue
|
||||
}
|
||||
|
||||
results.add(use);
|
||||
return count == null || results.length < count;
|
||||
|
|
@ -395,10 +400,7 @@ class AnalyticsDatabase with DatabaseFileStorage {
|
|||
);
|
||||
}
|
||||
|
||||
for (final u in usesForKey) {
|
||||
model.addUse(u);
|
||||
}
|
||||
|
||||
model.addUses(usesForKey);
|
||||
updates[key] = model;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -18,9 +18,13 @@ import 'package:fluffychat/pangea/analytics_summary/learning_progress_indicators
|
|||
import 'package:fluffychat/pangea/common/utils/error_handler.dart';
|
||||
import 'package:fluffychat/pangea/constructs/construct_identifier.dart';
|
||||
import 'package:fluffychat/pangea/constructs/construct_level_enum.dart';
|
||||
import 'package:fluffychat/pangea/events/models/pangea_token_model.dart';
|
||||
import 'package:fluffychat/pangea/lemmas/lemma_info_response.dart';
|
||||
import 'package:fluffychat/pangea/morphs/default_morph_mapping.dart';
|
||||
import 'package:fluffychat/pangea/morphs/morph_models.dart';
|
||||
import 'package:fluffychat/pangea/morphs/morph_repo.dart';
|
||||
import 'package:fluffychat/pangea/token_info_feedback/show_token_feedback_dialog.dart';
|
||||
import 'package:fluffychat/pangea/token_info_feedback/token_info_feedback_request.dart';
|
||||
import 'package:fluffychat/widgets/matrix.dart';
|
||||
|
||||
class ConstructAnalyticsView extends StatefulWidget {
|
||||
|
|
@ -49,6 +53,7 @@ class ConstructAnalyticsViewState extends State<ConstructAnalyticsView> {
|
|||
FocusNode searchFocusNode = FocusNode();
|
||||
ConstructLevelEnum? selectedConstructLevel;
|
||||
StreamSubscription<AnalyticsStreamUpdate>? _constructUpdateSub;
|
||||
final ValueNotifier<int> reloadNotifier = ValueNotifier<int>(0);
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
|
|
@ -72,6 +77,7 @@ class ConstructAnalyticsViewState extends State<ConstructAnalyticsView> {
|
|||
searchController.dispose();
|
||||
_constructUpdateSub?.cancel();
|
||||
searchFocusNode.dispose();
|
||||
reloadNotifier.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
|
|
@ -162,6 +168,29 @@ class ConstructAnalyticsViewState extends State<ConstructAnalyticsView> {
|
|||
});
|
||||
}
|
||||
|
||||
Future<void> onFlagTokenInfo(
|
||||
PangeaToken token,
|
||||
LemmaInfoResponse lemmaInfo,
|
||||
String phonetics,
|
||||
) async {
|
||||
final requestData = TokenInfoFeedbackRequestData(
|
||||
userId: Matrix.of(context).client.userID!,
|
||||
detectedLanguage: MatrixState.pangeaController.userController.userL2Code!,
|
||||
tokens: [token],
|
||||
selectedToken: 0,
|
||||
wordCardL1: MatrixState.pangeaController.userController.userL1Code!,
|
||||
lemmaInfo: lemmaInfo,
|
||||
phonetics: phonetics,
|
||||
);
|
||||
|
||||
await TokenFeedbackUtil.showTokenFeedbackDialog(
|
||||
context,
|
||||
requestData: requestData,
|
||||
langCode: MatrixState.pangeaController.userController.userL2Code!,
|
||||
onUpdated: () => reloadNotifier.value++,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
|
|
@ -182,7 +211,10 @@ class ConstructAnalyticsViewState extends State<ConstructAnalyticsView> {
|
|||
: MorphDetailsView(constructId: widget.construct!)
|
||||
: widget.construct == null
|
||||
? VocabAnalyticsListView(controller: this)
|
||||
: VocabDetailsView(constructId: widget.construct!),
|
||||
: VocabDetailsView(
|
||||
constructId: widget.construct!,
|
||||
controller: this,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
|
|
|
|||
|
|
@ -1,10 +1,10 @@
|
|||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:fluffychat/pangea/analytics_details_popup/analytics_details_usage_content.dart';
|
||||
import 'package:fluffychat/pangea/analytics_details_popup/construct_xp_progress_bar.dart';
|
||||
import 'package:fluffychat/pangea/analytics_details_popup/morph_meaning_widget.dart';
|
||||
import 'package:fluffychat/pangea/constructs/construct_identifier.dart';
|
||||
import 'package:fluffychat/pangea/constructs/construct_level_enum.dart';
|
||||
import 'package:fluffychat/pangea/lemmas/construct_xp_widget.dart';
|
||||
import 'package:fluffychat/pangea/morphs/morph_feature_display.dart';
|
||||
import 'package:fluffychat/pangea/morphs/morph_features_enum.dart';
|
||||
import 'package:fluffychat/pangea/morphs/morph_tag_display.dart';
|
||||
|
|
@ -54,11 +54,7 @@ class MorphDetailsView extends StatelessWidget {
|
|||
),
|
||||
const Divider(),
|
||||
if (construct != null) ...[
|
||||
ConstructXpWidget(
|
||||
icon: construct.lemmaCategory.icon(30.0),
|
||||
level: construct.lemmaCategory,
|
||||
points: construct.points,
|
||||
),
|
||||
ConstructXPProgressBar(construct: construct.id),
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(20.0),
|
||||
child: AnalyticsDetailsUsageContent(
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ import 'package:flutter/material.dart';
|
|||
import 'package:collection/collection.dart';
|
||||
|
||||
import 'package:fluffychat/l10n/l10n.dart';
|
||||
import 'package:fluffychat/pangea/analytics_details_popup/analytics_details_popup.dart';
|
||||
import 'package:fluffychat/pangea/analytics_details_popup/analytics_details_usage_content.dart';
|
||||
import 'package:fluffychat/pangea/analytics_details_popup/construct_xp_progress_bar.dart';
|
||||
import 'package:fluffychat/pangea/analytics_details_popup/word_text_with_audio_button.dart';
|
||||
|
|
@ -12,8 +13,6 @@ import 'package:fluffychat/pangea/events/models/pangea_token_model.dart';
|
|||
import 'package:fluffychat/pangea/events/models/pangea_token_text_model.dart';
|
||||
import 'package:fluffychat/pangea/lemmas/lemma.dart';
|
||||
import 'package:fluffychat/pangea/lemmas/lemma_info_response.dart';
|
||||
import 'package:fluffychat/pangea/token_info_feedback/show_token_feedback_dialog.dart';
|
||||
import 'package:fluffychat/pangea/token_info_feedback/token_info_feedback_request.dart';
|
||||
import 'package:fluffychat/pangea/toolbar/word_card/word_zoom_widget.dart';
|
||||
import 'package:fluffychat/widgets/adaptive_dialogs/show_ok_cancel_alert_dialog.dart';
|
||||
import 'package:fluffychat/widgets/future_loading_dialog.dart';
|
||||
|
|
@ -23,10 +22,12 @@ import 'package:fluffychat/widgets/matrix.dart';
|
|||
/// Displays information about selected lemma, and its usage
|
||||
class VocabDetailsView extends StatelessWidget {
|
||||
final ConstructIdentifier constructId;
|
||||
final ConstructAnalyticsViewState controller;
|
||||
|
||||
const VocabDetailsView({
|
||||
super.key,
|
||||
required this.constructId,
|
||||
required this.controller,
|
||||
});
|
||||
|
||||
Future<void> _blockLemma(BuildContext context) async {
|
||||
|
|
@ -93,27 +94,12 @@ class VocabDetailsView extends StatelessWidget {
|
|||
MatrixState.pangeaController.userController.userL2Code!,
|
||||
construct: constructId,
|
||||
onClose: Navigator.of(context).pop,
|
||||
onFlagTokenInfo:
|
||||
(LemmaInfoResponse lemmaInfo, String phonetics) {
|
||||
final requestData = TokenInfoFeedbackRequestData(
|
||||
userId: Matrix.of(context).client.userID!,
|
||||
detectedLanguage: MatrixState
|
||||
.pangeaController.userController.userL2Code!,
|
||||
tokens: [token],
|
||||
selectedToken: 0,
|
||||
wordCardL1: MatrixState
|
||||
.pangeaController.userController.userL1Code!,
|
||||
lemmaInfo: lemmaInfo,
|
||||
phonetics: phonetics,
|
||||
);
|
||||
|
||||
TokenFeedbackUtil.showTokenFeedbackDialog(
|
||||
context,
|
||||
requestData: requestData,
|
||||
langCode: MatrixState
|
||||
.pangeaController.userController.userL2Code!,
|
||||
);
|
||||
},
|
||||
onFlagTokenInfo: (
|
||||
LemmaInfoResponse lemmaInfo,
|
||||
String phonetics,
|
||||
) =>
|
||||
controller.onFlagTokenInfo(token, lemmaInfo, phonetics),
|
||||
reloadNotifier: controller.reloadNotifier,
|
||||
maxWidth: double.infinity,
|
||||
),
|
||||
),
|
||||
|
|
|
|||
|
|
@ -140,8 +140,8 @@ class ConstructUses {
|
|||
_uses.sort((a, b) => a.timeStamp.compareTo(b.timeStamp));
|
||||
}
|
||||
|
||||
void addUse(OneConstructUse use) {
|
||||
_uses.add(use);
|
||||
void addUses(List<OneConstructUse> uses) {
|
||||
_uses.addAll(uses);
|
||||
_sortUses();
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -86,6 +86,10 @@ enum ConstructUseTypeEnum {
|
|||
// grammar category activity
|
||||
corGC,
|
||||
incGC,
|
||||
|
||||
// grammar error activity
|
||||
corGE,
|
||||
incGE,
|
||||
}
|
||||
|
||||
extension ConstructUseTypeExtension on ConstructUseTypeEnum {
|
||||
|
|
@ -171,6 +175,10 @@ extension ConstructUseTypeExtension on ConstructUseTypeEnum {
|
|||
return L10n.of(context).constructUseCorGCDesc;
|
||||
case ConstructUseTypeEnum.incGC:
|
||||
return L10n.of(context).constructUseIncGCDesc;
|
||||
case ConstructUseTypeEnum.corGE:
|
||||
return L10n.of(context).constructUseCorGEDesc;
|
||||
case ConstructUseTypeEnum.incGE:
|
||||
return L10n.of(context).constructUseIncGEDesc;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -213,6 +221,8 @@ extension ConstructUseTypeExtension on ConstructUseTypeEnum {
|
|||
case ConstructUseTypeEnum.ignM:
|
||||
case ConstructUseTypeEnum.corGC:
|
||||
case ConstructUseTypeEnum.incGC:
|
||||
case ConstructUseTypeEnum.corGE:
|
||||
case ConstructUseTypeEnum.incGE:
|
||||
return ActivityTypeEnum.morphId.icon;
|
||||
case ConstructUseTypeEnum.em:
|
||||
return ActivityTypeEnum.emoji.icon;
|
||||
|
|
@ -246,6 +256,7 @@ extension ConstructUseTypeExtension on ConstructUseTypeEnum {
|
|||
case ConstructUseTypeEnum.corLM:
|
||||
case ConstructUseTypeEnum.corLA:
|
||||
case ConstructUseTypeEnum.corGC:
|
||||
case ConstructUseTypeEnum.corGE:
|
||||
return 5;
|
||||
|
||||
case ConstructUseTypeEnum.pvm:
|
||||
|
|
@ -287,6 +298,7 @@ extension ConstructUseTypeExtension on ConstructUseTypeEnum {
|
|||
case ConstructUseTypeEnum.incLM:
|
||||
case ConstructUseTypeEnum.incLA:
|
||||
case ConstructUseTypeEnum.incGC:
|
||||
case ConstructUseTypeEnum.incGE:
|
||||
return -1;
|
||||
|
||||
case ConstructUseTypeEnum.incPA:
|
||||
|
|
@ -340,6 +352,8 @@ extension ConstructUseTypeExtension on ConstructUseTypeEnum {
|
|||
case ConstructUseTypeEnum.bonus:
|
||||
case ConstructUseTypeEnum.corGC:
|
||||
case ConstructUseTypeEnum.incGC:
|
||||
case ConstructUseTypeEnum.corGE:
|
||||
case ConstructUseTypeEnum.incGE:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
|
@ -385,6 +399,8 @@ extension ConstructUseTypeExtension on ConstructUseTypeEnum {
|
|||
case ConstructUseTypeEnum.incLM:
|
||||
case ConstructUseTypeEnum.corGC:
|
||||
case ConstructUseTypeEnum.incGC:
|
||||
case ConstructUseTypeEnum.corGE:
|
||||
case ConstructUseTypeEnum.incGE:
|
||||
return LearningSkillsEnum.reading;
|
||||
case ConstructUseTypeEnum.pvm:
|
||||
return LearningSkillsEnum.speaking;
|
||||
|
|
@ -415,6 +431,7 @@ extension ConstructUseTypeExtension on ConstructUseTypeEnum {
|
|||
case ConstructUseTypeEnum.corLM:
|
||||
case ConstructUseTypeEnum.corLA:
|
||||
case ConstructUseTypeEnum.corGC:
|
||||
case ConstructUseTypeEnum.corGE:
|
||||
return SpaceAnalyticsSummaryEnum.numChoicesCorrect;
|
||||
|
||||
case ConstructUseTypeEnum.incIt:
|
||||
|
|
@ -428,6 +445,7 @@ extension ConstructUseTypeExtension on ConstructUseTypeEnum {
|
|||
case ConstructUseTypeEnum.incLM:
|
||||
case ConstructUseTypeEnum.incLA:
|
||||
case ConstructUseTypeEnum.incGC:
|
||||
case ConstructUseTypeEnum.incGE:
|
||||
return SpaceAnalyticsSummaryEnum.numChoicesIncorrect;
|
||||
|
||||
case ConstructUseTypeEnum.ignIt:
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -1,10 +1,7 @@
|
|||
import 'dart:async';
|
||||
import 'dart:collection';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:collection/collection.dart';
|
||||
|
||||
import 'package:fluffychat/l10n/l10n.dart';
|
||||
import 'package:fluffychat/pangea/analytics_data/analytics_data_service.dart';
|
||||
import 'package:fluffychat/pangea/analytics_data/analytics_updater_mixin.dart';
|
||||
|
|
@ -26,6 +23,7 @@ import 'package:fluffychat/pangea/text_to_speech/tts_controller.dart';
|
|||
import 'package:fluffychat/pangea/toolbar/message_practice/practice_record_controller.dart';
|
||||
import 'package:fluffychat/widgets/future_loading_dialog.dart';
|
||||
import 'package:fluffychat/widgets/matrix.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class SelectedMorphChoice {
|
||||
final MorphFeaturesEnum feature;
|
||||
|
|
@ -37,18 +35,28 @@ class SelectedMorphChoice {
|
|||
});
|
||||
}
|
||||
|
||||
class PracticeChoice {
|
||||
class VocabPracticeChoice {
|
||||
final String choiceId;
|
||||
final String choiceText;
|
||||
final String? choiceEmoji;
|
||||
|
||||
const PracticeChoice({
|
||||
const VocabPracticeChoice({
|
||||
required this.choiceId,
|
||||
required this.choiceText,
|
||||
this.choiceEmoji,
|
||||
});
|
||||
}
|
||||
|
||||
class _PracticeQueueEntry {
|
||||
final MessageActivityRequest request;
|
||||
final Completer<MultipleChoicePracticeActivityModel> completer;
|
||||
|
||||
_PracticeQueueEntry({
|
||||
required this.request,
|
||||
required this.completer,
|
||||
});
|
||||
}
|
||||
|
||||
class SessionLoader extends AsyncLoader<AnalyticsPracticeSessionModel> {
|
||||
final ConstructTypeEnum type;
|
||||
SessionLoader({required this.type});
|
||||
|
|
@ -59,6 +67,8 @@ class SessionLoader extends AsyncLoader<AnalyticsPracticeSessionModel> {
|
|||
}
|
||||
|
||||
class AnalyticsPractice extends StatefulWidget {
|
||||
static bool bypassExitConfirmation = false;
|
||||
|
||||
final ConstructTypeEnum type;
|
||||
const AnalyticsPractice({
|
||||
super.key,
|
||||
|
|
@ -76,14 +86,13 @@ class AnalyticsPracticeState extends State<AnalyticsPractice>
|
|||
final ValueNotifier<AsyncState<MultipleChoicePracticeActivityModel>>
|
||||
activityState = ValueNotifier(const AsyncState.idle());
|
||||
|
||||
final Queue<
|
||||
MapEntry<PracticeTarget,
|
||||
Completer<MultipleChoicePracticeActivityModel>>> _queue = Queue();
|
||||
final Queue<_PracticeQueueEntry> _queue = Queue();
|
||||
|
||||
final ValueNotifier<PracticeTarget?> activityTarget =
|
||||
ValueNotifier<PracticeTarget?>(null);
|
||||
final ValueNotifier<MessageActivityRequest?> activityTarget =
|
||||
ValueNotifier<MessageActivityRequest?>(null);
|
||||
|
||||
final ValueNotifier<double> progressNotifier = ValueNotifier<double>(0.0);
|
||||
final ValueNotifier<bool> enableChoicesNotifier = ValueNotifier<bool>(true);
|
||||
|
||||
final ValueNotifier<SelectedMorphChoice?> selectedMorphChoice =
|
||||
ValueNotifier<SelectedMorphChoice?>(null);
|
||||
|
|
@ -106,11 +115,6 @@ class AnalyticsPracticeState extends State<AnalyticsPractice>
|
|||
@override
|
||||
void dispose() {
|
||||
_languageStreamSubscription?.cancel();
|
||||
if (_isComplete) {
|
||||
AnalyticsPracticeSessionRepo.clear();
|
||||
} else {
|
||||
_saveSession();
|
||||
}
|
||||
_sessionLoader.dispose();
|
||||
activityState.dispose();
|
||||
activityTarget.dispose();
|
||||
|
|
@ -134,13 +138,13 @@ class AnalyticsPracticeState extends State<AnalyticsPractice>
|
|||
AnalyticsDataService get _analyticsService =>
|
||||
Matrix.of(context).analyticsDataService;
|
||||
|
||||
List<PracticeChoice> filteredChoices(
|
||||
List<VocabPracticeChoice> filteredChoices(
|
||||
MultipleChoicePracticeActivityModel activity,
|
||||
) {
|
||||
final content = activity.multipleChoiceContent;
|
||||
final choices = content.choices.toList();
|
||||
final answer = content.answers.first;
|
||||
final filtered = <PracticeChoice>[];
|
||||
final filtered = <VocabPracticeChoice>[];
|
||||
|
||||
final seenTexts = <String>{};
|
||||
for (final id in choices) {
|
||||
|
|
@ -155,7 +159,7 @@ class AnalyticsPracticeState extends State<AnalyticsPractice>
|
|||
(choice) => choice.choiceText == text,
|
||||
);
|
||||
if (index != -1) {
|
||||
filtered[index] = PracticeChoice(
|
||||
filtered[index] = VocabPracticeChoice(
|
||||
choiceId: id,
|
||||
choiceText: text,
|
||||
choiceEmoji: getChoiceEmoji(activity.storageKey, id),
|
||||
|
|
@ -166,7 +170,7 @@ class AnalyticsPracticeState extends State<AnalyticsPractice>
|
|||
|
||||
seenTexts.add(text);
|
||||
filtered.add(
|
||||
PracticeChoice(
|
||||
VocabPracticeChoice(
|
||||
choiceId: id,
|
||||
choiceText: text,
|
||||
choiceEmoji: getChoiceEmoji(activity.storageKey, id),
|
||||
|
|
@ -221,20 +225,11 @@ class AnalyticsPracticeState extends State<AnalyticsPractice>
|
|||
if (activityTarget.value == null) return;
|
||||
if (widget.type != ConstructTypeEnum.vocab) return;
|
||||
TtsController.tryToSpeak(
|
||||
activityTarget.value!.tokens.first.vocabConstructID.lemma,
|
||||
activityTarget.value!.target.tokens.first.vocabConstructID.lemma,
|
||||
langCode: MatrixState.pangeaController.userController.userL2!.langCode,
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> _saveSession() async {
|
||||
if (_sessionLoader.isLoaded) {
|
||||
await AnalyticsPracticeSessionRepo.update(
|
||||
widget.type,
|
||||
_sessionLoader.value!,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _waitForAnalytics() async {
|
||||
if (!_analyticsService.initCompleter.isCompleted) {
|
||||
MatrixState.pangeaController.initControllers();
|
||||
|
|
@ -269,7 +264,7 @@ class AnalyticsPracticeState extends State<AnalyticsPractice>
|
|||
Future<void> reloadSession() async {
|
||||
_resetActivityState();
|
||||
_resetSessionState();
|
||||
await AnalyticsPracticeSessionRepo.clear();
|
||||
|
||||
_sessionLoader.reset();
|
||||
await _startSession();
|
||||
}
|
||||
|
|
@ -284,7 +279,7 @@ class AnalyticsPracticeState extends State<AnalyticsPractice>
|
|||
bonus,
|
||||
forceUpdate: true,
|
||||
);
|
||||
await _saveSession();
|
||||
AnalyticsPractice.bypassExitConfirmation = true;
|
||||
}
|
||||
|
||||
bool _continuing = false;
|
||||
|
|
@ -292,6 +287,7 @@ class AnalyticsPracticeState extends State<AnalyticsPractice>
|
|||
Future<void> _continueSession() async {
|
||||
if (_continuing) return;
|
||||
_continuing = true;
|
||||
enableChoicesNotifier.value = true;
|
||||
|
||||
try {
|
||||
if (activityState.value
|
||||
|
|
@ -304,10 +300,10 @@ class AnalyticsPracticeState extends State<AnalyticsPractice>
|
|||
selectedMorphChoice.value = null;
|
||||
final nextActivityCompleter = _queue.removeFirst();
|
||||
|
||||
activityTarget.value = nextActivityCompleter.key;
|
||||
activityTarget.value = nextActivityCompleter.request;
|
||||
_playAudio();
|
||||
|
||||
final activity = await nextActivityCompleter.value.future;
|
||||
final activity = await nextActivityCompleter.completer.future;
|
||||
activityState.value = AsyncState.loaded(activity);
|
||||
}
|
||||
} catch (e) {
|
||||
|
|
@ -327,7 +323,7 @@ class AnalyticsPracticeState extends State<AnalyticsPractice>
|
|||
activityState.value = const AsyncState.loading();
|
||||
final req = requests.first;
|
||||
|
||||
activityTarget.value = req.target;
|
||||
activityTarget.value = req;
|
||||
_playAudio();
|
||||
|
||||
final res = await _fetchActivity(req);
|
||||
|
|
@ -343,10 +339,17 @@ class AnalyticsPracticeState extends State<AnalyticsPractice>
|
|||
_fillActivityQueue(requests.skip(1).toList());
|
||||
}
|
||||
|
||||
Future<void> _fillActivityQueue(List<MessageActivityRequest> requests) async {
|
||||
Future<void> _fillActivityQueue(
|
||||
List<MessageActivityRequest> requests,
|
||||
) async {
|
||||
for (final request in requests) {
|
||||
final completer = Completer<MultipleChoicePracticeActivityModel>();
|
||||
_queue.add(MapEntry(request.target, completer));
|
||||
_queue.add(
|
||||
_PracticeQueueEntry(
|
||||
request: request,
|
||||
completer: completer,
|
||||
),
|
||||
);
|
||||
|
||||
try {
|
||||
final res = await _fetchActivity(request);
|
||||
|
|
@ -425,6 +428,10 @@ class AnalyticsPracticeState extends State<AnalyticsPractice>
|
|||
tag: choiceContent,
|
||||
);
|
||||
}
|
||||
final isCorrect = activity.multipleChoiceContent.isCorrect(choiceContent);
|
||||
if (isCorrect) {
|
||||
enableChoicesNotifier.value = false;
|
||||
}
|
||||
|
||||
// Update activity record
|
||||
PracticeRecordController.onSelectChoice(
|
||||
|
|
@ -438,7 +445,6 @@ class AnalyticsPracticeState extends State<AnalyticsPractice>
|
|||
await _analyticsService.updateService
|
||||
.addAnalytics(choiceTargetId(choiceContent), [use]);
|
||||
|
||||
await _saveSession();
|
||||
if (!activity.multipleChoiceContent.isCorrect(choiceContent)) return;
|
||||
|
||||
_playAudio();
|
||||
|
|
@ -449,7 +455,6 @@ class AnalyticsPracticeState extends State<AnalyticsPractice>
|
|||
// Then mark this activity as completed, and either load the next or complete the session
|
||||
_sessionLoader.value!.completeActivity();
|
||||
progressNotifier.value = _sessionLoader.value!.progress;
|
||||
await _saveSession();
|
||||
|
||||
_isComplete ? await _completeSession() : await _continueSession();
|
||||
}
|
||||
|
|
@ -458,12 +463,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) {
|
||||
|
|
|
|||
|
|
@ -6,9 +6,32 @@ import 'package:fluffychat/pangea/analytics_practice/analytics_practice_constant
|
|||
import 'package:fluffychat/pangea/practice_activities/message_activity_request.dart';
|
||||
import 'package:fluffychat/pangea/practice_activities/practice_target.dart';
|
||||
|
||||
class AnalyticsActivityTarget {
|
||||
final PracticeTarget target;
|
||||
final GrammarErrorRequestInfo? grammarErrorInfo;
|
||||
|
||||
AnalyticsActivityTarget({
|
||||
required this.target,
|
||||
this.grammarErrorInfo,
|
||||
});
|
||||
|
||||
Map<String, dynamic> toJson() => {
|
||||
'target': target.toJson(),
|
||||
'grammarErrorInfo': grammarErrorInfo?.toJson(),
|
||||
};
|
||||
|
||||
factory AnalyticsActivityTarget.fromJson(Map<String, dynamic> json) =>
|
||||
AnalyticsActivityTarget(
|
||||
target: PracticeTarget.fromJson(json['target']),
|
||||
grammarErrorInfo: json['grammarErrorInfo'] != null
|
||||
? GrammarErrorRequestInfo.fromJson(json['grammarErrorInfo'])
|
||||
: null,
|
||||
);
|
||||
}
|
||||
|
||||
class AnalyticsPracticeSessionModel {
|
||||
final DateTime startedAt;
|
||||
final List<PracticeTarget> practiceTargets;
|
||||
final List<AnalyticsActivityTarget> practiceTargets;
|
||||
final String userL1;
|
||||
final String userL2;
|
||||
|
||||
|
|
@ -38,7 +61,8 @@ class AnalyticsPracticeSessionModel {
|
|||
userL1: userL1,
|
||||
userL2: userL2,
|
||||
activityQualityFeedback: null,
|
||||
target: target,
|
||||
target: target.target,
|
||||
grammarErrorInfo: target.grammarErrorInfo,
|
||||
);
|
||||
}).toList();
|
||||
}
|
||||
|
|
@ -59,8 +83,8 @@ class AnalyticsPracticeSessionModel {
|
|||
return AnalyticsPracticeSessionModel(
|
||||
startedAt: DateTime.parse(json['startedAt'] as String),
|
||||
practiceTargets: (json['practiceTargets'] as List<dynamic>)
|
||||
.map((e) => PracticeTarget.fromJson(e))
|
||||
.whereType<PracticeTarget>()
|
||||
.map((e) => AnalyticsActivityTarget.fromJson(e))
|
||||
.whereType<AnalyticsActivityTarget>()
|
||||
.toList(),
|
||||
userL1: json['userL1'] as String,
|
||||
userL2: json['userL2'] as String,
|
||||
|
|
|
|||
|
|
@ -1,30 +1,24 @@
|
|||
import 'dart:math';
|
||||
|
||||
import 'package:get_storage/get_storage.dart';
|
||||
|
||||
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/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';
|
||||
import 'package:fluffychat/pangea/events/models/pangea_token_text_model.dart';
|
||||
import 'package:fluffychat/pangea/lemmas/lemma.dart';
|
||||
import 'package:fluffychat/pangea/morphs/morph_features_enum.dart';
|
||||
import 'package:fluffychat/pangea/practice_activities/activity_type_enum.dart';
|
||||
import 'package:fluffychat/pangea/practice_activities/message_activity_request.dart';
|
||||
import 'package:fluffychat/pangea/practice_activities/practice_target.dart';
|
||||
import 'package:fluffychat/widgets/matrix.dart';
|
||||
|
||||
class AnalyticsPracticeSessionRepo {
|
||||
static final GetStorage _storage = GetStorage('practice_session');
|
||||
|
||||
static Future<AnalyticsPracticeSessionModel> get(
|
||||
ConstructTypeEnum type,
|
||||
) async {
|
||||
final cached = _getCached(type);
|
||||
if (cached != null) {
|
||||
return cached;
|
||||
}
|
||||
|
||||
final r = Random();
|
||||
final activityTypes = ActivityTypeEnum.analyticsPracticeTypes(type);
|
||||
|
||||
|
|
@ -33,28 +27,44 @@ class AnalyticsPracticeSessionRepo {
|
|||
(_) => activityTypes[r.nextInt(activityTypes.length)],
|
||||
);
|
||||
|
||||
final List<PracticeTarget> targets = [];
|
||||
final List<AnalyticsActivityTarget> targets = [];
|
||||
|
||||
if (type == ConstructTypeEnum.vocab) {
|
||||
final constructs = await _fetchVocab();
|
||||
final targetCount = min(constructs.length, types.length);
|
||||
targets.addAll([
|
||||
for (var i = 0; i < targetCount; i++)
|
||||
PracticeTarget(
|
||||
tokens: [constructs[i].asToken],
|
||||
activityType: types[i],
|
||||
AnalyticsActivityTarget(
|
||||
target: PracticeTarget(
|
||||
tokens: [constructs[i].asToken],
|
||||
activityType: types[i],
|
||||
),
|
||||
),
|
||||
]);
|
||||
} else {
|
||||
final morphs = await _fetchMorphs();
|
||||
targets.addAll([
|
||||
for (final entry in morphs.entries)
|
||||
PracticeTarget(
|
||||
tokens: [entry.key],
|
||||
activityType: types[targets.length],
|
||||
morphFeature: entry.value,
|
||||
),
|
||||
]);
|
||||
final errorTargets = await _fetchErrors();
|
||||
targets.addAll(errorTargets);
|
||||
|
||||
if (targets.length < AnalyticsPracticeConstants.practiceGroupSize) {
|
||||
final morphs = await _fetchMorphs();
|
||||
final remainingCount =
|
||||
AnalyticsPracticeConstants.practiceGroupSize - targets.length;
|
||||
final morphEntries = morphs.entries.take(remainingCount);
|
||||
|
||||
for (final entry in morphEntries) {
|
||||
targets.add(
|
||||
AnalyticsActivityTarget(
|
||||
target: PracticeTarget(
|
||||
tokens: [entry.key],
|
||||
activityType: types[targets.length],
|
||||
morphFeature: entry.value,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
targets.shuffle();
|
||||
}
|
||||
}
|
||||
|
||||
final session = AnalyticsPracticeSessionModel(
|
||||
|
|
@ -63,18 +73,9 @@ class AnalyticsPracticeSessionRepo {
|
|||
startedAt: DateTime.now(),
|
||||
practiceTargets: targets,
|
||||
);
|
||||
await _setCached(type, session);
|
||||
return session;
|
||||
}
|
||||
|
||||
static Future<void> update(
|
||||
ConstructTypeEnum type,
|
||||
AnalyticsPracticeSessionModel session,
|
||||
) =>
|
||||
_setCached(type, session);
|
||||
|
||||
static Future<void> clear() => _storage.erase();
|
||||
|
||||
static Future<List<ConstructIdentifier>> _fetchVocab() async {
|
||||
final constructs = await MatrixState
|
||||
.pangeaController.matrixState.analyticsDataService
|
||||
|
|
@ -163,27 +164,98 @@ class AnalyticsPracticeSessionRepo {
|
|||
return targets;
|
||||
}
|
||||
|
||||
static AnalyticsPracticeSessionModel? _getCached(
|
||||
ConstructTypeEnum type,
|
||||
) {
|
||||
try {
|
||||
final entry = _storage.read(type.name);
|
||||
if (entry == null) return null;
|
||||
final json = entry as Map<String, dynamic>;
|
||||
return AnalyticsPracticeSessionModel.fromJson(json);
|
||||
} catch (e) {
|
||||
_storage.remove(type.name);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
static Future<List<AnalyticsActivityTarget>> _fetchErrors() async {
|
||||
final uses = await MatrixState
|
||||
.pangeaController.matrixState.analyticsDataService
|
||||
.getUses(count: 100, type: ConstructUseTypeEnum.ga);
|
||||
|
||||
static Future<void> _setCached(
|
||||
ConstructTypeEnum type,
|
||||
AnalyticsPracticeSessionModel session,
|
||||
) async {
|
||||
await _storage.write(
|
||||
type.name,
|
||||
session.toJson(),
|
||||
);
|
||||
final client = MatrixState.pangeaController.matrixState.client;
|
||||
final Map<String, PangeaMessageEvent?> idsToEvents = {};
|
||||
|
||||
for (final use in uses) {
|
||||
final eventID = use.metadata.eventId;
|
||||
if (eventID == null || idsToEvents.containsKey(eventID)) continue;
|
||||
|
||||
final roomID = use.metadata.roomId;
|
||||
if (roomID == null) {
|
||||
idsToEvents[eventID] = null;
|
||||
continue;
|
||||
}
|
||||
|
||||
final room = client.getRoomById(roomID);
|
||||
final event = await room?.getEventById(eventID);
|
||||
if (event == null || event.redacted) {
|
||||
idsToEvents[eventID] = null;
|
||||
continue;
|
||||
}
|
||||
|
||||
final timeline = await room!.getTimeline();
|
||||
idsToEvents[eventID] = PangeaMessageEvent(
|
||||
event: event,
|
||||
timeline: timeline,
|
||||
ownMessage: event.senderId == client.userID,
|
||||
);
|
||||
}
|
||||
|
||||
final l2Code =
|
||||
MatrixState.pangeaController.userController.userL2!.langCodeShort;
|
||||
|
||||
final events = idsToEvents.values.whereType<PangeaMessageEvent>().toList();
|
||||
final eventsWithContent = events.where((e) {
|
||||
final originalSent = e.originalSent;
|
||||
final choreo = originalSent?.choreo;
|
||||
final tokens = originalSent?.tokens;
|
||||
return originalSent?.langCode.split("-").first == l2Code &&
|
||||
choreo != null &&
|
||||
tokens != null &&
|
||||
tokens.isNotEmpty &&
|
||||
choreo.choreoSteps.any(
|
||||
(step) =>
|
||||
step.acceptedOrIgnoredMatch?.isGrammarMatch == true &&
|
||||
step.acceptedOrIgnoredMatch?.match.bestChoice != null,
|
||||
);
|
||||
});
|
||||
|
||||
final targets = <AnalyticsActivityTarget>[];
|
||||
for (final event in eventsWithContent) {
|
||||
final originalSent = event.originalSent!;
|
||||
final choreo = originalSent.choreo!;
|
||||
final tokens = originalSent.tokens!;
|
||||
|
||||
for (int i = 0; i < choreo.choreoSteps.length; i++) {
|
||||
final step = choreo.choreoSteps[i];
|
||||
final igcMatch = step.acceptedOrIgnoredMatch;
|
||||
if (igcMatch?.isGrammarMatch != true ||
|
||||
igcMatch?.match.bestChoice == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
final choices = igcMatch!.match.choices!.map((c) => c.value).toList();
|
||||
final choiceTokens = tokens.where(
|
||||
(token) =>
|
||||
token.lemma.saveVocab &&
|
||||
choices.any(
|
||||
(choice) => choice.contains(token.text.content),
|
||||
),
|
||||
);
|
||||
|
||||
targets.add(
|
||||
AnalyticsActivityTarget(
|
||||
target: PracticeTarget(
|
||||
tokens: choiceTokens.toList(),
|
||||
activityType: ActivityTypeEnum.grammarError,
|
||||
morphFeature: null,
|
||||
),
|
||||
grammarErrorInfo: GrammarErrorRequestInfo(
|
||||
choreo: choreo,
|
||||
stepIndex: i,
|
||||
eventID: event.eventId,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return targets;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,3 @@
|
|||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:fluffychat/config/app_config.dart';
|
||||
import 'package:fluffychat/l10n/l10n.dart';
|
||||
import 'package:fluffychat/pangea/analytics_details_popup/morph_meaning_widget.dart';
|
||||
|
|
@ -22,6 +20,7 @@ import 'package:fluffychat/pangea/practice_activities/activity_type_enum.dart';
|
|||
import 'package:fluffychat/pangea/practice_activities/practice_activity_model.dart';
|
||||
import 'package:fluffychat/widgets/layouts/max_width_body.dart';
|
||||
import 'package:fluffychat/widgets/matrix.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class AnalyticsPracticeView extends StatelessWidget {
|
||||
final AnalyticsPracticeState controller;
|
||||
|
|
@ -144,8 +143,8 @@ class _AnalyticsActivityView extends StatelessWidget {
|
|||
if (controller.widget.type ==
|
||||
ConstructTypeEnum.vocab)
|
||||
PhoneticTranscriptionWidget(
|
||||
text:
|
||||
target.tokens.first.vocabConstructID.lemma,
|
||||
text: target
|
||||
.target.tokens.first.vocabConstructID.lemma,
|
||||
textLanguage: MatrixState
|
||||
.pangeaController.userController.userL2!,
|
||||
style: const TextStyle(fontSize: 14.0),
|
||||
|
|
@ -158,13 +157,8 @@ class _AnalyticsActivityView extends StatelessWidget {
|
|||
Expanded(
|
||||
flex: 2,
|
||||
child: Center(
|
||||
child: ValueListenableBuilder(
|
||||
valueListenable: controller.activityTarget,
|
||||
builder: (context, target, __) => target != null
|
||||
? _ExampleMessageWidget(
|
||||
controller.getExampleMessage(target),
|
||||
)
|
||||
: const SizedBox(),
|
||||
child: _AnalyticsPracticeCenterContent(
|
||||
controller: controller,
|
||||
),
|
||||
),
|
||||
),
|
||||
|
|
@ -180,6 +174,36 @@ class _AnalyticsActivityView extends StatelessWidget {
|
|||
}
|
||||
}
|
||||
|
||||
class _AnalyticsPracticeCenterContent extends StatelessWidget {
|
||||
final AnalyticsPracticeState controller;
|
||||
|
||||
const _AnalyticsPracticeCenterContent({
|
||||
required this.controller,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return ValueListenableBuilder(
|
||||
valueListenable: controller.activityTarget,
|
||||
builder: (context, target, __) => switch (target?.target.activityType) {
|
||||
null => const SizedBox(),
|
||||
ActivityTypeEnum.grammarError => ValueListenableBuilder(
|
||||
valueListenable: controller.activityState,
|
||||
builder: (context, state, __) => switch (state) {
|
||||
AsyncLoaded(value: final activity) => _ErrorBlankWidget(
|
||||
activity: activity as GrammarErrorPracticeActivityModel,
|
||||
),
|
||||
_ => const SizedBox(),
|
||||
},
|
||||
),
|
||||
_ => _ExampleMessageWidget(
|
||||
controller.getExampleMessage(target!.target),
|
||||
),
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _ExampleMessageWidget extends StatelessWidget {
|
||||
final Future<List<InlineSpan>?> future;
|
||||
|
||||
|
|
@ -221,6 +245,62 @@ class _ExampleMessageWidget extends StatelessWidget {
|
|||
}
|
||||
}
|
||||
|
||||
class _ErrorBlankWidget extends StatelessWidget {
|
||||
final GrammarErrorPracticeActivityModel activity;
|
||||
|
||||
const _ErrorBlankWidget({
|
||||
required this.activity,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final text = activity.text;
|
||||
final errorOffset = activity.errorOffset;
|
||||
final errorLength = activity.errorLength;
|
||||
|
||||
return Container(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 12,
|
||||
vertical: 8,
|
||||
),
|
||||
decoration: BoxDecoration(
|
||||
color: Color.alphaBlend(
|
||||
Colors.white.withAlpha(180),
|
||||
ThemeData.dark().colorScheme.primary,
|
||||
),
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
),
|
||||
child: RichText(
|
||||
text: TextSpan(
|
||||
style: TextStyle(
|
||||
color: Theme.of(context).colorScheme.onPrimaryFixed,
|
||||
fontSize: AppConfig.fontSizeFactor * AppConfig.messageFontSize,
|
||||
),
|
||||
children: [
|
||||
if (errorOffset > 0)
|
||||
TextSpan(text: text.characters.take(errorOffset).toString()),
|
||||
WidgetSpan(
|
||||
child: Container(
|
||||
height: 4.0,
|
||||
width: (errorLength * 8).toDouble(),
|
||||
padding: const EdgeInsets.only(bottom: 2.0),
|
||||
decoration: BoxDecoration(
|
||||
color: Theme.of(context).colorScheme.primary,
|
||||
),
|
||||
),
|
||||
),
|
||||
if (errorOffset + errorLength < text.length)
|
||||
TextSpan(
|
||||
text:
|
||||
text.characters.skip(errorOffset + errorLength).toString(),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _ActivityChoicesWidget extends StatelessWidget {
|
||||
final AnalyticsPracticeState controller;
|
||||
|
||||
|
|
@ -328,6 +408,7 @@ class _ChoiceCard extends StatelessWidget {
|
|||
|
||||
final String choiceText;
|
||||
final String? choiceEmoji;
|
||||
final bool enabled;
|
||||
|
||||
const _ChoiceCard({
|
||||
required this.activity,
|
||||
|
|
@ -337,6 +418,7 @@ class _ChoiceCard extends StatelessWidget {
|
|||
required this.cardHeight,
|
||||
required this.choiceText,
|
||||
required this.choiceEmoji,
|
||||
this.enabled = true,
|
||||
});
|
||||
|
||||
@override
|
||||
|
|
@ -358,6 +440,7 @@ class _ChoiceCard extends StatelessWidget {
|
|||
onPressed: onPressed,
|
||||
isCorrect: isCorrect,
|
||||
height: cardHeight,
|
||||
isEnabled: enabled,
|
||||
);
|
||||
|
||||
case ActivityTypeEnum.lemmaAudio:
|
||||
|
|
@ -370,6 +453,7 @@ class _ChoiceCard extends StatelessWidget {
|
|||
onPressed: onPressed,
|
||||
isCorrect: isCorrect,
|
||||
height: cardHeight,
|
||||
isEnabled: enabled,
|
||||
);
|
||||
|
||||
case ActivityTypeEnum.grammarCategory:
|
||||
|
|
@ -383,6 +467,22 @@ class _ChoiceCard extends StatelessWidget {
|
|||
tag: choiceText,
|
||||
onPressed: onPressed,
|
||||
isCorrect: isCorrect,
|
||||
enabled: enabled,
|
||||
);
|
||||
|
||||
case ActivityTypeEnum.grammarError:
|
||||
final activity = this.activity as GrammarErrorPracticeActivityModel;
|
||||
return GameChoiceCard(
|
||||
key: ValueKey(
|
||||
'${activity.errorLength}_${activity.errorOffset}_${activity.eventID}_${activityType.name}_grammar_error_$choiceId',
|
||||
),
|
||||
shouldFlip: false,
|
||||
targetId: targetId,
|
||||
onPressed: onPressed,
|
||||
isCorrect: isCorrect,
|
||||
height: cardHeight,
|
||||
isEnabled: enabled,
|
||||
child: Text(choiceText),
|
||||
);
|
||||
|
||||
default:
|
||||
|
|
@ -395,6 +495,7 @@ class _ChoiceCard extends StatelessWidget {
|
|||
onPressed: onPressed,
|
||||
isCorrect: isCorrect,
|
||||
height: cardHeight,
|
||||
isEnabled: enabled,
|
||||
child: Text(choiceText),
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -1,9 +1,8 @@
|
|||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:fluffychat/pangea/analytics_practice/choice_cards/game_choice_card.dart';
|
||||
import 'package:fluffychat/pangea/morphs/get_grammar_copy.dart';
|
||||
import 'package:fluffychat/pangea/morphs/morph_features_enum.dart';
|
||||
import 'package:fluffychat/pangea/morphs/morph_icon.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
/// Choice card for meaning activity with emoji, and alt text on flip
|
||||
class GrammarChoiceCard extends StatelessWidget {
|
||||
|
|
@ -16,6 +15,7 @@ class GrammarChoiceCard extends StatelessWidget {
|
|||
final VoidCallback onPressed;
|
||||
final bool isCorrect;
|
||||
final double height;
|
||||
final bool enabled;
|
||||
|
||||
const GrammarChoiceCard({
|
||||
required this.choiceId,
|
||||
|
|
@ -25,6 +25,7 @@ class GrammarChoiceCard extends StatelessWidget {
|
|||
required this.onPressed,
|
||||
required this.isCorrect,
|
||||
this.height = 72.0,
|
||||
this.enabled = true,
|
||||
super.key,
|
||||
});
|
||||
|
||||
|
|
@ -47,6 +48,7 @@ class GrammarChoiceCard extends StatelessWidget {
|
|||
onPressed: onPressed,
|
||||
isCorrect: isCorrect,
|
||||
height: height,
|
||||
isEnabled: enabled,
|
||||
child: Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: [
|
||||
|
|
|
|||
|
|
@ -1,5 +1,7 @@
|
|||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:go_router/go_router.dart';
|
||||
|
||||
import 'package:fluffychat/config/app_config.dart';
|
||||
import 'package:fluffychat/l10n/l10n.dart';
|
||||
import 'package:fluffychat/pangea/analytics_misc/level_up/star_rain_widget.dart';
|
||||
|
|
@ -162,7 +164,9 @@ class CompletedActivitySessionView extends StatelessWidget {
|
|||
vertical: 8.0,
|
||||
),
|
||||
),
|
||||
onPressed: () => Navigator.of(context).pop(),
|
||||
onPressed: () {
|
||||
context.go('/rooms/analytics/vocab');
|
||||
},
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
|
|
|
|||
|
|
@ -0,0 +1,51 @@
|
|||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:fluffychat/pangea/practice_activities/message_activity_request.dart';
|
||||
import 'package:fluffychat/pangea/practice_activities/multiple_choice_activity_model.dart';
|
||||
import 'package:fluffychat/pangea/practice_activities/practice_activity_model.dart';
|
||||
|
||||
class GrammarErrorPracticeGenerator {
|
||||
static Future<MessageActivityResponse> get(
|
||||
MessageActivityRequest req,
|
||||
) async {
|
||||
assert(
|
||||
req.grammarErrorInfo != null,
|
||||
'Grammar error info must be provided for grammar error practice',
|
||||
);
|
||||
|
||||
final choreo = req.grammarErrorInfo!.choreo;
|
||||
final stepIndex = req.grammarErrorInfo!.stepIndex;
|
||||
final eventID = req.grammarErrorInfo!.eventID;
|
||||
|
||||
final igcMatch =
|
||||
choreo.choreoSteps[stepIndex].acceptedOrIgnoredMatch?.match;
|
||||
assert(igcMatch?.choices != null, 'IGC match must have choices');
|
||||
assert(igcMatch?.bestChoice != null, 'IGC match must have a best choice');
|
||||
|
||||
final correctChoice = igcMatch!.bestChoice!.value;
|
||||
final choices = igcMatch.choices!.map((c) => c.value).toList();
|
||||
|
||||
final stepText = choreo.stepText(stepIndex: stepIndex - 1);
|
||||
final errorSpan = stepText.characters
|
||||
.skip(igcMatch.offset)
|
||||
.take(igcMatch.length)
|
||||
.toString();
|
||||
|
||||
choices.add(errorSpan);
|
||||
choices.shuffle();
|
||||
return MessageActivityResponse(
|
||||
activity: GrammarErrorPracticeActivityModel(
|
||||
tokens: req.target.tokens,
|
||||
langCode: req.userL2,
|
||||
multipleChoiceContent: MultipleChoiceActivity(
|
||||
choices: choices.toSet(),
|
||||
answers: {correctChoice},
|
||||
),
|
||||
text: stepText,
|
||||
errorOffset: igcMatch.offset,
|
||||
errorLength: igcMatch.length,
|
||||
eventID: eventID,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -206,21 +206,23 @@ class LearningProgressIndicators extends StatelessWidget {
|
|||
child: FutureBuilder(
|
||||
future: analyticsService.derivedData,
|
||||
builder: (context, snapshot) {
|
||||
final cached =
|
||||
analyticsService.cachedDerivedData;
|
||||
final data = snapshot.data ?? cached;
|
||||
return Row(
|
||||
spacing: 8.0,
|
||||
children: [
|
||||
Expanded(
|
||||
child: LearningProgressBar(
|
||||
height: 24.0,
|
||||
loading: !snapshot.hasData,
|
||||
progress: snapshot
|
||||
.data?.levelProgress ??
|
||||
0.0,
|
||||
loading: data == null,
|
||||
progress:
|
||||
data?.levelProgress ?? 0.0,
|
||||
),
|
||||
),
|
||||
if (snapshot.hasData)
|
||||
if (data != null)
|
||||
Text(
|
||||
"⭐ ${snapshot.data!.level}",
|
||||
"⭐ ${data.level}",
|
||||
style: Theme.of(context)
|
||||
.textTheme
|
||||
.titleLarge
|
||||
|
|
|
|||
|
|
@ -1,4 +1,3 @@
|
|||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:dropdown_button2/dropdown_button2.dart';
|
||||
|
|
@ -155,6 +154,7 @@ class BotChatSettingsDialogState extends State<BotChatSettingsDialog> {
|
|||
onChanged: _setLevel,
|
||||
enabled: !widget.room.isActivitySession,
|
||||
width: 300,
|
||||
maxHeight: 300,
|
||||
),
|
||||
DropdownButtonFormField2<String>(
|
||||
customButton: _selectedVoice != null
|
||||
|
|
@ -171,7 +171,7 @@ class BotChatSettingsDialogState extends State<BotChatSettingsDialog> {
|
|||
),
|
||||
isExpanded: true,
|
||||
dropdownStyleData: DropdownStyleData(
|
||||
maxHeight: kIsWeb ? 250 : null,
|
||||
maxHeight: 250,
|
||||
decoration: BoxDecoration(
|
||||
color: Theme.of(context).colorScheme.surfaceContainerHigh,
|
||||
borderRadius: BorderRadius.circular(14.0),
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -14,6 +14,7 @@ class LanguageLevelDropdown extends StatelessWidget {
|
|||
final bool enabled;
|
||||
final Color? backgroundColor;
|
||||
final double? width;
|
||||
final double? maxHeight;
|
||||
|
||||
const LanguageLevelDropdown({
|
||||
super.key,
|
||||
|
|
@ -23,6 +24,7 @@ class LanguageLevelDropdown extends StatelessWidget {
|
|||
this.enabled = true,
|
||||
this.backgroundColor,
|
||||
this.width,
|
||||
this.maxHeight,
|
||||
});
|
||||
|
||||
@override
|
||||
|
|
@ -46,7 +48,7 @@ class LanguageLevelDropdown extends StatelessWidget {
|
|||
),
|
||||
isExpanded: true,
|
||||
dropdownStyleData: DropdownStyleData(
|
||||
maxHeight: kIsWeb ? 500 : null,
|
||||
maxHeight: maxHeight ?? (kIsWeb ? 500 : null),
|
||||
decoration: BoxDecoration(
|
||||
color: backgroundColor ??
|
||||
Theme.of(context).colorScheme.surfaceContainerHigh,
|
||||
|
|
|
|||
|
|
@ -132,6 +132,9 @@ class SpanData {
|
|||
return choices![index];
|
||||
}
|
||||
|
||||
String get errorSpan =>
|
||||
fullText.characters.skip(offset).take(length).toString();
|
||||
|
||||
bool isNormalizationError() {
|
||||
final correctChoice = choices
|
||||
?.firstWhereOrNull(
|
||||
|
|
@ -139,8 +142,6 @@ class SpanData {
|
|||
)
|
||||
?.value;
|
||||
|
||||
final errorSpan = fullText.characters.skip(offset).take(length).toString();
|
||||
|
||||
final l2Code =
|
||||
MatrixState.pangeaController.userController.userL2?.langCodeShort;
|
||||
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
@ -293,12 +294,14 @@ class PangeaMessageEvent {
|
|||
RepresentationEvent? representationByLanguage(
|
||||
String langCode, {
|
||||
bool Function(RepresentationEvent)? filter,
|
||||
}) =>
|
||||
representations.firstWhereOrNull(
|
||||
(element) =>
|
||||
element.langCode.split("-")[0] == langCode.split("-")[0] &&
|
||||
(filter?.call(element) ?? true),
|
||||
);
|
||||
}) {
|
||||
representations.firstWhereOrNull(
|
||||
(element) =>
|
||||
element.langCode.split("-")[0] == langCode.split("-")[0] &&
|
||||
(filter?.call(element) ?? true),
|
||||
);
|
||||
return null;
|
||||
}
|
||||
|
||||
Event? getTextToSpeechLocal(String langCode, String text) {
|
||||
for (final audio in allAudio) {
|
||||
|
|
@ -492,7 +495,7 @@ class PangeaMessageEvent {
|
|||
_representations = null;
|
||||
return room.sendPangeaEvent(
|
||||
content: representation.toJson(),
|
||||
parentEventId: eventId,
|
||||
parentEventId: _latestEdit.eventId,
|
||||
type: PangeaEventTypes.representation,
|
||||
);
|
||||
}
|
||||
|
|
@ -586,6 +589,7 @@ class PangeaMessageEvent {
|
|||
}
|
||||
|
||||
Future<String> requestRespresentationByL1() async {
|
||||
debugPrint("LATEST EDIT: ${_latestEdit.toJson()}");
|
||||
if (_l1Code == null || _l2Code == null) {
|
||||
throw Exception("Missing language codes");
|
||||
}
|
||||
|
|
@ -597,7 +601,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 +620,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 +674,7 @@ class PangeaMessageEvent {
|
|||
) async {
|
||||
final repEvent = await room.sendPangeaEvent(
|
||||
content: representation.toJson(),
|
||||
parentEventId: eventId,
|
||||
parentEventId: _latestEdit.eventId,
|
||||
type: PangeaEventTypes.representation,
|
||||
);
|
||||
return repEvent?.eventId;
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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(
|
||||
|
|
|
|||
|
|
@ -1,37 +0,0 @@
|
|||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:fluffychat/pangea/constructs/construct_level_enum.dart';
|
||||
|
||||
class ConstructXpWidget extends StatelessWidget {
|
||||
final ConstructLevelEnum level;
|
||||
final int points;
|
||||
final Widget icon;
|
||||
|
||||
const ConstructXpWidget({
|
||||
super.key,
|
||||
required this.level,
|
||||
required this.points,
|
||||
required this.icon,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final Color textColor = Theme.of(context).brightness != Brightness.light
|
||||
? level.color(context)
|
||||
: level.darkColor(context);
|
||||
|
||||
return Row(
|
||||
spacing: 16.0,
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
icon,
|
||||
Text(
|
||||
"$points XP",
|
||||
style: Theme.of(context).textTheme.titleMedium?.copyWith(
|
||||
color: textColor,
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -6,6 +6,7 @@ import 'package:shimmer/shimmer.dart';
|
|||
|
||||
import 'package:fluffychat/config/app_config.dart';
|
||||
import 'package:fluffychat/pangea/analytics_data/analytics_updater_mixin.dart';
|
||||
import 'package:fluffychat/pangea/common/utils/async_state.dart';
|
||||
import 'package:fluffychat/pangea/common/widgets/shimmer_background.dart';
|
||||
import 'package:fluffychat/pangea/constructs/construct_identifier.dart';
|
||||
import 'package:fluffychat/pangea/lemmas/lemma_meaning_builder.dart';
|
||||
|
|
@ -49,35 +50,15 @@ class LemmaHighlightEmojiRowState extends State<LemmaHighlightEmojiRow>
|
|||
constructId: widget.cId,
|
||||
messageInfo: widget.messageInfo,
|
||||
builder: (context, controller) {
|
||||
if (controller.error != null) {
|
||||
return const SizedBox.shrink();
|
||||
}
|
||||
|
||||
final emojis = controller.lemmaInfo?.emoji;
|
||||
return SizedBox(
|
||||
height: 70.0,
|
||||
child: Row(
|
||||
spacing: 4.0,
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: emojis == null || emojis.isEmpty
|
||||
? List.generate(
|
||||
3,
|
||||
(_) => Shimmer.fromColors(
|
||||
baseColor: Colors.transparent,
|
||||
highlightColor:
|
||||
Theme.of(context).colorScheme.primary.withAlpha(70),
|
||||
child: Container(
|
||||
height: 55.0,
|
||||
width: 55.0,
|
||||
decoration: BoxDecoration(
|
||||
color: Theme.of(context).colorScheme.primary,
|
||||
borderRadius:
|
||||
BorderRadius.circular(AppConfig.borderRadius),
|
||||
),
|
||||
),
|
||||
),
|
||||
)
|
||||
: emojis.map(
|
||||
return switch (controller.state) {
|
||||
AsyncError() => const SizedBox.shrink(),
|
||||
AsyncLoaded(value: final lemmaInfo) => SizedBox(
|
||||
height: 70.0,
|
||||
child: Row(
|
||||
spacing: 4.0,
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
...lemmaInfo.emoji.map(
|
||||
(emoji) {
|
||||
final targetId = "${widget.targetId}-$emoji";
|
||||
return EmojiChoiceItem(
|
||||
|
|
@ -94,9 +75,35 @@ class LemmaHighlightEmojiRowState extends State<LemmaHighlightEmojiRow>
|
|||
enabled: widget.enabled,
|
||||
);
|
||||
},
|
||||
).toList(),
|
||||
),
|
||||
);
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
_ => SizedBox(
|
||||
height: 70.0,
|
||||
child: Row(
|
||||
spacing: 4.0,
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: List.generate(
|
||||
3,
|
||||
(_) => Shimmer.fromColors(
|
||||
baseColor: Colors.transparent,
|
||||
highlightColor:
|
||||
Theme.of(context).colorScheme.primary.withAlpha(70),
|
||||
child: Container(
|
||||
height: 55.0,
|
||||
width: 55.0,
|
||||
decoration: BoxDecoration(
|
||||
color: Theme.of(context).colorScheme.primary,
|
||||
borderRadius:
|
||||
BorderRadius.circular(AppConfig.borderRadius),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
};
|
||||
},
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,29 +8,11 @@ import 'package:fluffychat/pangea/lemmas/lemma_info_request.dart';
|
|||
import 'package:fluffychat/pangea/lemmas/lemma_info_response.dart';
|
||||
import 'package:fluffychat/widgets/matrix.dart';
|
||||
|
||||
class _LemmaMeaningLoader extends AsyncLoader<LemmaInfoResponse> {
|
||||
final LemmaInfoRequest request;
|
||||
_LemmaMeaningLoader(this.request) : super();
|
||||
|
||||
@override
|
||||
Future<LemmaInfoResponse> fetch() async {
|
||||
final result = await LemmaInfoRepo.get(
|
||||
MatrixState.pangeaController.userController.accessToken,
|
||||
request,
|
||||
);
|
||||
|
||||
if (result.isError) {
|
||||
throw result.asError!.error;
|
||||
}
|
||||
|
||||
return result.asValue!.value;
|
||||
}
|
||||
}
|
||||
|
||||
class LemmaMeaningBuilder extends StatefulWidget {
|
||||
final String langCode;
|
||||
final ConstructIdentifier constructId;
|
||||
final Map<String, dynamic> messageInfo;
|
||||
final ValueNotifier<int>? reloadNotifier;
|
||||
|
||||
final Widget Function(
|
||||
BuildContext context,
|
||||
|
|
@ -43,6 +25,7 @@ class LemmaMeaningBuilder extends StatefulWidget {
|
|||
required this.constructId,
|
||||
required this.builder,
|
||||
required this.messageInfo,
|
||||
this.reloadNotifier,
|
||||
});
|
||||
|
||||
@override
|
||||
|
|
@ -50,12 +33,14 @@ class LemmaMeaningBuilder extends StatefulWidget {
|
|||
}
|
||||
|
||||
class LemmaMeaningBuilderState extends State<LemmaMeaningBuilder> {
|
||||
late _LemmaMeaningLoader _loader;
|
||||
final ValueNotifier<AsyncState<LemmaInfoResponse>> _loader =
|
||||
ValueNotifier(const AsyncState.idle());
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_reload();
|
||||
_load();
|
||||
widget.reloadNotifier?.addListener(_load);
|
||||
}
|
||||
|
||||
@override
|
||||
|
|
@ -63,24 +48,22 @@ class LemmaMeaningBuilderState extends State<LemmaMeaningBuilder> {
|
|||
super.didUpdateWidget(oldWidget);
|
||||
if (oldWidget.constructId != widget.constructId ||
|
||||
oldWidget.langCode != widget.langCode) {
|
||||
_loader.dispose();
|
||||
_reload();
|
||||
_load();
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
widget.reloadNotifier?.removeListener(_load);
|
||||
_loader.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
bool get isLoading => _loader.isLoading;
|
||||
bool get isError => _loader.isError;
|
||||
|
||||
Object? get error =>
|
||||
isError ? (_loader.state.value as AsyncError).error : null;
|
||||
|
||||
LemmaInfoResponse? get lemmaInfo => _loader.value;
|
||||
AsyncState<LemmaInfoResponse> get state => _loader.value;
|
||||
bool get isError => _loader.value is AsyncError;
|
||||
bool get isLoaded => _loader.value is AsyncLoaded;
|
||||
LemmaInfoResponse? get lemmaInfo =>
|
||||
isLoaded ? (_loader.value as AsyncLoaded<LemmaInfoResponse>).value : null;
|
||||
|
||||
LemmaInfoRequest get _request => LemmaInfoRequest(
|
||||
lemma: widget.constructId.lemma,
|
||||
|
|
@ -91,15 +74,23 @@ class LemmaMeaningBuilderState extends State<LemmaMeaningBuilder> {
|
|||
messageInfo: widget.messageInfo,
|
||||
);
|
||||
|
||||
void _reload() {
|
||||
_loader = _LemmaMeaningLoader(_request);
|
||||
_loader.load();
|
||||
Future<void> _load() async {
|
||||
_loader.value = const AsyncState.loading();
|
||||
final result = await LemmaInfoRepo.get(
|
||||
MatrixState.pangeaController.userController.accessToken,
|
||||
_request,
|
||||
);
|
||||
|
||||
if (!mounted) return;
|
||||
result.isError
|
||||
? _loader.value = AsyncState.error(result.asError!.error)
|
||||
: _loader.value = AsyncState.loaded(result.asValue!.value);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return ValueListenableBuilder(
|
||||
valueListenable: _loader.state,
|
||||
valueListenable: _loader,
|
||||
builder: (context, _, __) => widget.builder(
|
||||
context,
|
||||
this,
|
||||
|
|
|
|||
|
|
@ -1,77 +0,0 @@
|
|||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:fluffychat/l10n/l10n.dart';
|
||||
import 'package:fluffychat/pangea/analytics_misc/text_loading_shimmer.dart';
|
||||
import 'package:fluffychat/pangea/common/network/requests.dart';
|
||||
import 'package:fluffychat/pangea/common/widgets/error_indicator.dart';
|
||||
import 'package:fluffychat/pangea/constructs/construct_identifier.dart';
|
||||
import 'package:fluffychat/pangea/lemmas/lemma_meaning_builder.dart';
|
||||
import 'package:fluffychat/widgets/matrix.dart';
|
||||
|
||||
class LemmaMeaningWidget extends StatelessWidget {
|
||||
final ConstructIdentifier constructId;
|
||||
final TextStyle? style;
|
||||
final InlineSpan? leading;
|
||||
final Map<String, dynamic> messageInfo;
|
||||
|
||||
const LemmaMeaningWidget({
|
||||
super.key,
|
||||
required this.constructId,
|
||||
required this.messageInfo,
|
||||
this.style,
|
||||
this.leading,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return LemmaMeaningBuilder(
|
||||
langCode: MatrixState.pangeaController.userController.userL2!.langCode,
|
||||
constructId: constructId,
|
||||
messageInfo: messageInfo,
|
||||
builder: (context, controller) {
|
||||
if (controller.isLoading) {
|
||||
return const TextLoadingShimmer();
|
||||
}
|
||||
|
||||
if (controller.error != null) {
|
||||
if (controller.error is UnsubscribedException) {
|
||||
return ErrorIndicator(
|
||||
message: L10n.of(context).subscribeToUnlockDefinitions,
|
||||
style: style,
|
||||
onTap: () {
|
||||
MatrixState.pangeaController.subscriptionController
|
||||
.showPaywall(context);
|
||||
},
|
||||
);
|
||||
}
|
||||
return ErrorIndicator(
|
||||
message: L10n.of(context).errorFetchingDefinition,
|
||||
style: style,
|
||||
);
|
||||
}
|
||||
|
||||
return Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Flexible(
|
||||
child: RichText(
|
||||
textAlign: leading == null ? TextAlign.center : TextAlign.start,
|
||||
text: TextSpan(
|
||||
style: style,
|
||||
children: [
|
||||
if (leading != null) leading!,
|
||||
if (leading != null)
|
||||
const WidgetSpan(child: SizedBox(width: 6.0)),
|
||||
TextSpan(
|
||||
text: controller.lemmaInfo?.meaning,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -46,8 +46,8 @@ class SignupPageView extends StatelessWidget {
|
|||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
const PangeaSsoButton(provider: SSOProvider.google),
|
||||
const PangeaSsoButton(provider: SSOProvider.apple),
|
||||
const PangeaSsoButton(provider: SSOProvider.google),
|
||||
ElevatedButton(
|
||||
onPressed: () => context.go(
|
||||
'/home/language/signup/email',
|
||||
|
|
|
|||
|
|
@ -9,12 +9,12 @@ import 'package:matrix/matrix.dart';
|
|||
import 'package:universal_html/html.dart' as html;
|
||||
|
||||
import 'package:fluffychat/config/app_config.dart';
|
||||
import 'package:fluffychat/pages/homeserver_picker/homeserver_picker.dart';
|
||||
import 'package:fluffychat/pangea/common/utils/firebase_analytics.dart';
|
||||
import 'package:fluffychat/pangea/login/sso_provider_enum.dart';
|
||||
import 'package:fluffychat/pangea/login/widgets/p_sso_dialog.dart';
|
||||
import 'package:fluffychat/utils/platform_infos.dart';
|
||||
import 'package:fluffychat/widgets/fluffy_chat_app.dart';
|
||||
import 'package:fluffychat/widgets/future_loading_dialog.dart';
|
||||
import 'package:fluffychat/widgets/matrix.dart';
|
||||
|
||||
class PangeaSsoButton extends StatelessWidget {
|
||||
|
|
@ -27,23 +27,25 @@ class PangeaSsoButton extends StatelessWidget {
|
|||
super.key,
|
||||
});
|
||||
|
||||
Future<void> _runSSOLogin(BuildContext context) => showAdaptiveDialog(
|
||||
context: context,
|
||||
builder: (context) => SSODialog(
|
||||
future: () => _ssoAction(
|
||||
IdentityProvider(
|
||||
id: provider.id,
|
||||
name: provider.name,
|
||||
),
|
||||
context,
|
||||
),
|
||||
),
|
||||
);
|
||||
Future<void> _runSSOLogin(BuildContext context) async {
|
||||
final token = await showAdaptiveDialog<String?>(
|
||||
context: context,
|
||||
builder: (context) => SSODialog(
|
||||
future: () => _getSSOToken(context),
|
||||
),
|
||||
);
|
||||
|
||||
Future<void> _ssoAction(
|
||||
IdentityProvider provider,
|
||||
BuildContext context,
|
||||
) async {
|
||||
if (token == null || token.isEmpty) {
|
||||
return;
|
||||
}
|
||||
|
||||
await showFutureLoadingDialog(
|
||||
context: context,
|
||||
future: () => _ssoAction(token, context),
|
||||
);
|
||||
}
|
||||
|
||||
Future<String?> _getSSOToken(BuildContext context) async {
|
||||
final bool isDefaultPlatform = (PlatformInfos.isMobile ||
|
||||
PlatformInfos.isWeb ||
|
||||
PlatformInfos.isMacOS);
|
||||
|
|
@ -58,7 +60,7 @@ class PangeaSsoButton extends StatelessWidget {
|
|||
: 'http://localhost:3001//login';
|
||||
final client = await Matrix.of(context).getLoginClient();
|
||||
final url = client.homeserver!.replace(
|
||||
path: '/_matrix/client/v3/login/sso/redirect/${provider.id ?? ''}',
|
||||
path: '/_matrix/client/v3/login/sso/redirect/${provider.id}',
|
||||
queryParameters: {'redirectUrl': redirectUrl},
|
||||
);
|
||||
|
||||
|
|
@ -74,13 +76,20 @@ class PangeaSsoButton extends StatelessWidget {
|
|||
} catch (err) {
|
||||
if (err is PlatformException && err.code == 'CANCELED') {
|
||||
debugPrint("user cancelled SSO login");
|
||||
return;
|
||||
return null;
|
||||
}
|
||||
rethrow;
|
||||
}
|
||||
final token = Uri.parse(result).queryParameters['loginToken'];
|
||||
if (token?.isEmpty ?? false) return;
|
||||
if (token?.isEmpty ?? false) return null;
|
||||
return token;
|
||||
}
|
||||
|
||||
Future<void> _ssoAction(
|
||||
String token,
|
||||
BuildContext context,
|
||||
) async {
|
||||
final client = Matrix.of(context).client;
|
||||
final redirect = client.onLoginStateChanged.stream
|
||||
.where((state) => state == LoginState.loggedIn)
|
||||
.first
|
||||
|
|
@ -110,7 +119,7 @@ class PangeaSsoButton extends StatelessWidget {
|
|||
await redirect;
|
||||
}
|
||||
|
||||
GoogleAnalytics.login(provider.name!, loginRes.userId);
|
||||
GoogleAnalytics.login(provider.name, loginRes.userId);
|
||||
}
|
||||
|
||||
@override
|
||||
|
|
|
|||
|
|
@ -5,11 +5,10 @@ import 'package:flutter/material.dart';
|
|||
import 'package:flutter_linkify/flutter_linkify.dart';
|
||||
|
||||
import 'package:fluffychat/l10n/l10n.dart';
|
||||
import 'package:fluffychat/utils/localized_exception_extension.dart';
|
||||
import 'package:fluffychat/utils/url_launcher.dart';
|
||||
|
||||
class SSODialog extends StatefulWidget {
|
||||
final Future<void> Function() future;
|
||||
final Future<String?> Function() future;
|
||||
const SSODialog({
|
||||
super.key,
|
||||
required this.future,
|
||||
|
|
@ -22,7 +21,6 @@ class SSODialog extends StatefulWidget {
|
|||
class SSODialogState extends State<SSODialog> {
|
||||
Timer? _hintTimer;
|
||||
bool _showHint = false;
|
||||
Object? _error;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
|
|
@ -43,9 +41,10 @@ class SSODialogState extends State<SSODialog> {
|
|||
|
||||
Future<void> _runFuture() async {
|
||||
try {
|
||||
await widget.future();
|
||||
final token = await widget.future();
|
||||
Navigator.of(context).pop(token);
|
||||
} catch (e) {
|
||||
setState(() => _error = e);
|
||||
Navigator.of(context).pop();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -69,17 +68,11 @@ class SSODialogState extends State<SSODialog> {
|
|||
icon: const Icon(Icons.close),
|
||||
),
|
||||
),
|
||||
_error == null
|
||||
? Text(
|
||||
L10n.of(context).ssoDialogTitle,
|
||||
style: Theme.of(context).textTheme.headlineMedium,
|
||||
textAlign: TextAlign.center,
|
||||
)
|
||||
: Icon(
|
||||
Icons.error_outline_outlined,
|
||||
color: Theme.of(context).colorScheme.error,
|
||||
size: 48,
|
||||
),
|
||||
Text(
|
||||
L10n.of(context).ssoDialogTitle,
|
||||
style: Theme.of(context).textTheme.headlineMedium,
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
Container(
|
||||
alignment: Alignment.center,
|
||||
constraints: const BoxConstraints(minHeight: 150),
|
||||
|
|
@ -88,39 +81,29 @@ class SSODialogState extends State<SSODialog> {
|
|||
spacing: 16.0,
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
if (_error != null)
|
||||
Text(
|
||||
_error!.toLocalizedString(context),
|
||||
style: Theme.of(context).textTheme.titleMedium,
|
||||
textAlign: TextAlign.center,
|
||||
)
|
||||
else ...[
|
||||
SelectableLinkify(
|
||||
text: L10n.of(context).ssoDialogDesc,
|
||||
textScaleFactor:
|
||||
MediaQuery.textScalerOf(context).scale(1),
|
||||
linkStyle: TextStyle(
|
||||
color: Theme.of(context).colorScheme.primary,
|
||||
decorationColor: Theme.of(context).colorScheme.primary,
|
||||
),
|
||||
options: const LinkifyOptions(humanize: false),
|
||||
onOpen: (url) =>
|
||||
UrlLauncher(context, url.url).launchUrl(),
|
||||
style: Theme.of(context).textTheme.bodyLarge,
|
||||
textAlign: TextAlign.center,
|
||||
SelectableLinkify(
|
||||
text: L10n.of(context).ssoDialogDesc,
|
||||
textScaleFactor: MediaQuery.textScalerOf(context).scale(1),
|
||||
linkStyle: TextStyle(
|
||||
color: Theme.of(context).colorScheme.primary,
|
||||
decorationColor: Theme.of(context).colorScheme.primary,
|
||||
),
|
||||
_showHint
|
||||
? Text(
|
||||
L10n.of(context).ssoDialogHelpText,
|
||||
style: Theme.of(context).textTheme.bodyLarge,
|
||||
textAlign: TextAlign.center,
|
||||
)
|
||||
: const SizedBox(
|
||||
height: 16.0,
|
||||
width: 16.0,
|
||||
child: CircularProgressIndicator.adaptive(),
|
||||
),
|
||||
],
|
||||
options: const LinkifyOptions(humanize: false),
|
||||
onOpen: (url) => UrlLauncher(context, url.url).launchUrl(),
|
||||
style: Theme.of(context).textTheme.bodyLarge,
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
_showHint
|
||||
? Text(
|
||||
L10n.of(context).ssoDialogHelpText,
|
||||
style: Theme.of(context).textTheme.bodyLarge,
|
||||
textAlign: TextAlign.center,
|
||||
)
|
||||
: const SizedBox(
|
||||
height: 16.0,
|
||||
width: 16.0,
|
||||
child: CircularProgressIndicator.adaptive(),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
|
|
|||
|
|
@ -8,29 +8,10 @@ import 'package:fluffychat/pangea/phonetic_transcription/phonetic_transcription_
|
|||
import 'package:fluffychat/widgets/matrix.dart';
|
||||
import 'phonetic_transcription_repo.dart';
|
||||
|
||||
class _TranscriptLoader extends AsyncLoader<String> {
|
||||
final PhoneticTranscriptionRequest request;
|
||||
_TranscriptLoader(this.request) : super();
|
||||
|
||||
@override
|
||||
Future<String> fetch() async {
|
||||
final resp = await PhoneticTranscriptionRepo.get(
|
||||
MatrixState.pangeaController.userController.accessToken,
|
||||
request,
|
||||
);
|
||||
|
||||
if (resp.isError) {
|
||||
throw resp.asError!.error;
|
||||
}
|
||||
|
||||
return resp.asValue!.value.phoneticTranscriptionResult.phoneticTranscription
|
||||
.first.phoneticL1Transcription.content;
|
||||
}
|
||||
}
|
||||
|
||||
class PhoneticTranscriptionBuilder extends StatefulWidget {
|
||||
final LanguageModel textLanguage;
|
||||
final String text;
|
||||
final ValueNotifier<int>? reloadNotifier;
|
||||
|
||||
final Widget Function(
|
||||
BuildContext context,
|
||||
|
|
@ -42,6 +23,7 @@ class PhoneticTranscriptionBuilder extends StatefulWidget {
|
|||
required this.textLanguage,
|
||||
required this.text,
|
||||
required this.builder,
|
||||
this.reloadNotifier,
|
||||
});
|
||||
|
||||
@override
|
||||
|
|
@ -51,12 +33,14 @@ class PhoneticTranscriptionBuilder extends StatefulWidget {
|
|||
|
||||
class PhoneticTranscriptionBuilderState
|
||||
extends State<PhoneticTranscriptionBuilder> {
|
||||
late _TranscriptLoader _loader;
|
||||
final ValueNotifier<AsyncState<String>> _loader =
|
||||
ValueNotifier(const AsyncState.idle());
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_reload();
|
||||
_load();
|
||||
widget.reloadNotifier?.addListener(_load);
|
||||
}
|
||||
|
||||
@override
|
||||
|
|
@ -64,27 +48,24 @@ class PhoneticTranscriptionBuilderState
|
|||
super.didUpdateWidget(oldWidget);
|
||||
if (oldWidget.text != widget.text ||
|
||||
oldWidget.textLanguage != widget.textLanguage) {
|
||||
_loader.dispose();
|
||||
_reload();
|
||||
_load();
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
widget.reloadNotifier?.removeListener(_load);
|
||||
_loader.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
bool get isLoading => _loader.isLoading;
|
||||
bool get isError => _loader.isError;
|
||||
AsyncState<String> get state => _loader.value;
|
||||
bool get isError => _loader.value is AsyncError;
|
||||
bool get isLoaded => _loader.value is AsyncLoaded;
|
||||
String? get transcription =>
|
||||
isLoaded ? (_loader.value as AsyncLoaded<String>).value : null;
|
||||
|
||||
Object? get error =>
|
||||
isError ? (_loader.state.value as AsyncError).error : null;
|
||||
|
||||
String? get transcription => _loader.value;
|
||||
|
||||
PhoneticTranscriptionRequest get _transcriptRequest =>
|
||||
PhoneticTranscriptionRequest(
|
||||
PhoneticTranscriptionRequest get _request => PhoneticTranscriptionRequest(
|
||||
arc: LanguageArc(
|
||||
l1: MatrixState.pangeaController.userController.userL1!,
|
||||
l2: widget.textLanguage,
|
||||
|
|
@ -92,15 +73,26 @@ class PhoneticTranscriptionBuilderState
|
|||
content: PangeaTokenText.fromString(widget.text),
|
||||
);
|
||||
|
||||
void _reload() {
|
||||
_loader = _TranscriptLoader(_transcriptRequest);
|
||||
_loader.load();
|
||||
Future<void> _load() async {
|
||||
_loader.value = const AsyncState.loading();
|
||||
final resp = await PhoneticTranscriptionRepo.get(
|
||||
MatrixState.pangeaController.userController.accessToken,
|
||||
_request,
|
||||
);
|
||||
|
||||
if (!mounted) return;
|
||||
resp.isError
|
||||
? _loader.value = AsyncState.error(resp.asError!.error)
|
||||
: _loader.value = AsyncState.loaded(
|
||||
resp.asValue!.value.phoneticTranscriptionResult
|
||||
.phoneticTranscription.first.phoneticL1Transcription.content,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return ValueListenableBuilder(
|
||||
valueListenable: _loader.state,
|
||||
valueListenable: _loader,
|
||||
builder: (context, _, __) => widget.builder(
|
||||
context,
|
||||
this,
|
||||
|
|
|
|||
|
|
@ -12,20 +12,47 @@ import 'package:fluffychat/pangea/common/utils/error_handler.dart';
|
|||
import 'package:fluffychat/pangea/phonetic_transcription/phonetic_transcription_request.dart';
|
||||
import 'package:fluffychat/pangea/phonetic_transcription/phonetic_transcription_response.dart';
|
||||
|
||||
class _PhoneticTranscriptionCacheItem {
|
||||
class _PhoneticTranscriptionMemoryCacheItem {
|
||||
final Future<Result<PhoneticTranscriptionResponse>> resultFuture;
|
||||
final DateTime timestamp;
|
||||
|
||||
const _PhoneticTranscriptionCacheItem({
|
||||
const _PhoneticTranscriptionMemoryCacheItem({
|
||||
required this.resultFuture,
|
||||
required this.timestamp,
|
||||
});
|
||||
}
|
||||
|
||||
class _PhoneticTranscriptionStorageCacheItem {
|
||||
final PhoneticTranscriptionResponse response;
|
||||
final DateTime timestamp;
|
||||
|
||||
const _PhoneticTranscriptionStorageCacheItem({
|
||||
required this.response,
|
||||
required this.timestamp,
|
||||
});
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
return {
|
||||
'response': response.toJson(),
|
||||
'timestamp': timestamp.toIso8601String(),
|
||||
};
|
||||
}
|
||||
|
||||
static _PhoneticTranscriptionStorageCacheItem fromJson(
|
||||
Map<String, dynamic> json,
|
||||
) {
|
||||
return _PhoneticTranscriptionStorageCacheItem(
|
||||
response: PhoneticTranscriptionResponse.fromJson(json['response']),
|
||||
timestamp: DateTime.parse(json['timestamp']),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class PhoneticTranscriptionRepo {
|
||||
// In-memory cache
|
||||
static final Map<String, _PhoneticTranscriptionCacheItem> _cache = {};
|
||||
static final Map<String, _PhoneticTranscriptionMemoryCacheItem> _cache = {};
|
||||
static const Duration _cacheDuration = Duration(minutes: 10);
|
||||
static const Duration _storageDuration = Duration(days: 7);
|
||||
|
||||
// Persistent storage
|
||||
static final GetStorage _storage =
|
||||
|
|
@ -53,7 +80,7 @@ class PhoneticTranscriptionRepo {
|
|||
final future = _safeFetch(accessToken, request);
|
||||
|
||||
// 4. Save to in-memory cache
|
||||
_cache[request.hashCode.toString()] = _PhoneticTranscriptionCacheItem(
|
||||
_cache[request.hashCode.toString()] = _PhoneticTranscriptionMemoryCacheItem(
|
||||
resultFuture: future,
|
||||
timestamp: DateTime.now(),
|
||||
);
|
||||
|
|
@ -71,7 +98,11 @@ class PhoneticTranscriptionRepo {
|
|||
await GetStorage.init('phonetic_transcription_storage');
|
||||
final key = request.hashCode.toString();
|
||||
try {
|
||||
await _storage.write(key, resultFuture.toJson());
|
||||
final item = _PhoneticTranscriptionStorageCacheItem(
|
||||
response: resultFuture,
|
||||
timestamp: DateTime.now(),
|
||||
);
|
||||
await _storage.write(key, item.toJson());
|
||||
_cache.remove(key); // Invalidate in-memory cache
|
||||
} catch (e, s) {
|
||||
ErrorHandler.logError(
|
||||
|
|
@ -154,7 +185,12 @@ class PhoneticTranscriptionRepo {
|
|||
final entry = _storage.read(key);
|
||||
if (entry == null) return null;
|
||||
|
||||
return PhoneticTranscriptionResponse.fromJson(entry);
|
||||
final item = _PhoneticTranscriptionStorageCacheItem.fromJson(entry);
|
||||
if (DateTime.now().difference(item.timestamp) >= _storageDuration) {
|
||||
_storage.remove(key);
|
||||
return null;
|
||||
}
|
||||
return item.response;
|
||||
} catch (e, s) {
|
||||
ErrorHandler.logError(
|
||||
e: e,
|
||||
|
|
|
|||
|
|
@ -1,10 +1,9 @@
|
|||
import 'dart:async';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:fluffychat/l10n/l10n.dart';
|
||||
import 'package:fluffychat/pangea/analytics_misc/text_loading_shimmer.dart';
|
||||
import 'package:fluffychat/pangea/common/network/requests.dart';
|
||||
import 'package:fluffychat/pangea/common/utils/async_state.dart';
|
||||
import 'package:fluffychat/pangea/common/widgets/error_indicator.dart';
|
||||
import 'package:fluffychat/pangea/languages/language_model.dart';
|
||||
import 'package:fluffychat/pangea/phonetic_transcription/phonetic_transcription_builder.dart';
|
||||
|
|
@ -22,6 +21,7 @@ class PhoneticTranscriptionWidget extends StatefulWidget {
|
|||
final int? maxLines;
|
||||
|
||||
final VoidCallback? onTranscriptionFetched;
|
||||
final ValueNotifier<int>? reloadNotifier;
|
||||
|
||||
const PhoneticTranscriptionWidget({
|
||||
super.key,
|
||||
|
|
@ -32,6 +32,7 @@ class PhoneticTranscriptionWidget extends StatefulWidget {
|
|||
this.iconColor,
|
||||
this.maxLines,
|
||||
this.onTranscriptionFetched,
|
||||
this.reloadNotifier,
|
||||
});
|
||||
|
||||
@override
|
||||
|
|
@ -68,77 +69,75 @@ class _PhoneticTranscriptionWidgetState
|
|||
final targetId = 'phonetic-transcription-${widget.text}-$hashCode';
|
||||
return HoverBuilder(
|
||||
builder: (context, hovering) {
|
||||
return GestureDetector(
|
||||
onTap: () => _handleAudioTap(targetId),
|
||||
child: AnimatedContainer(
|
||||
duration: const Duration(milliseconds: 150),
|
||||
decoration: BoxDecoration(
|
||||
color: hovering
|
||||
? Colors.grey.withAlpha((0.2 * 255).round())
|
||||
: Colors.transparent,
|
||||
borderRadius: BorderRadius.circular(6),
|
||||
),
|
||||
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4),
|
||||
child: CompositedTransformTarget(
|
||||
link: MatrixState.pAnyState.layerLinkAndKey(targetId).link,
|
||||
child: PhoneticTranscriptionBuilder(
|
||||
key: MatrixState.pAnyState.layerLinkAndKey(targetId).key,
|
||||
textLanguage: widget.textLanguage,
|
||||
text: widget.text,
|
||||
builder: (context, controller) {
|
||||
if (controller.isError) {
|
||||
return controller.error is UnsubscribedException
|
||||
? ErrorIndicator(
|
||||
message: L10n.of(context)
|
||||
.subscribeToUnlockTranscriptions,
|
||||
onTap: () {
|
||||
MatrixState
|
||||
.pangeaController.subscriptionController
|
||||
.showPaywall(context);
|
||||
},
|
||||
)
|
||||
: ErrorIndicator(
|
||||
message:
|
||||
L10n.of(context).failedToFetchTranscription,
|
||||
);
|
||||
}
|
||||
|
||||
if (controller.isLoading ||
|
||||
controller.transcription == null) {
|
||||
return const TextLoadingShimmer(
|
||||
width: 125.0,
|
||||
height: 20.0,
|
||||
);
|
||||
}
|
||||
|
||||
return Row(
|
||||
spacing: 8.0,
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Flexible(
|
||||
child: Text(
|
||||
controller.transcription!,
|
||||
textScaler: TextScaler.noScaling,
|
||||
style: widget.style ??
|
||||
Theme.of(context).textTheme.bodyMedium,
|
||||
maxLines: widget.maxLines,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
return Tooltip(
|
||||
message:
|
||||
_isPlaying ? L10n.of(context).stop : L10n.of(context).playAudio,
|
||||
child: GestureDetector(
|
||||
onTap: () => _handleAudioTap(targetId),
|
||||
child: AnimatedContainer(
|
||||
duration: const Duration(milliseconds: 150),
|
||||
decoration: BoxDecoration(
|
||||
color: hovering
|
||||
? Colors.grey.withAlpha((0.2 * 255).round())
|
||||
: Colors.transparent,
|
||||
borderRadius: BorderRadius.circular(6),
|
||||
),
|
||||
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4),
|
||||
child: CompositedTransformTarget(
|
||||
link: MatrixState.pAnyState.layerLinkAndKey(targetId).link,
|
||||
child: PhoneticTranscriptionBuilder(
|
||||
key: MatrixState.pAnyState.layerLinkAndKey(targetId).key,
|
||||
textLanguage: widget.textLanguage,
|
||||
text: widget.text,
|
||||
reloadNotifier: widget.reloadNotifier,
|
||||
builder: (context, controller) {
|
||||
return switch (controller.state) {
|
||||
AsyncError(error: final error) =>
|
||||
error is UnsubscribedException
|
||||
? ErrorIndicator(
|
||||
message: L10n.of(context)
|
||||
.subscribeToUnlockTranscriptions,
|
||||
onTap: () {
|
||||
MatrixState
|
||||
.pangeaController.subscriptionController
|
||||
.showPaywall(context);
|
||||
},
|
||||
)
|
||||
: ErrorIndicator(
|
||||
message:
|
||||
L10n.of(context).failedToFetchTranscription,
|
||||
),
|
||||
AsyncLoaded<String>(value: final transcription) => Row(
|
||||
spacing: 8.0,
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Flexible(
|
||||
child: Text(
|
||||
transcription,
|
||||
textScaler: TextScaler.noScaling,
|
||||
style: widget.style ??
|
||||
Theme.of(context).textTheme.bodyMedium,
|
||||
maxLines: widget.maxLines,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
),
|
||||
Icon(
|
||||
_isPlaying
|
||||
? Icons.pause_outlined
|
||||
: Icons.volume_up,
|
||||
size: widget.iconSize ?? 24,
|
||||
color: widget.iconColor ??
|
||||
Theme.of(context).iconTheme.color,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
Tooltip(
|
||||
message: _isPlaying
|
||||
? L10n.of(context).stop
|
||||
: L10n.of(context).playAudio,
|
||||
child: Icon(
|
||||
_isPlaying ? Icons.pause_outlined : Icons.volume_up,
|
||||
size: widget.iconSize ?? 24,
|
||||
color: widget.iconColor ??
|
||||
Theme.of(context).iconTheme.color,
|
||||
_ => const TextLoadingShimmer(
|
||||
width: 125.0,
|
||||
height: 20.0,
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
};
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
|
|
|
|||
|
|
@ -15,7 +15,8 @@ enum ActivityTypeEnum {
|
|||
messageMeaning,
|
||||
lemmaMeaning,
|
||||
lemmaAudio,
|
||||
grammarCategory;
|
||||
grammarCategory,
|
||||
grammarError;
|
||||
|
||||
bool get includeTTSOnClick {
|
||||
switch (this) {
|
||||
|
|
@ -30,6 +31,7 @@ enum ActivityTypeEnum {
|
|||
case ActivityTypeEnum.lemmaAudio:
|
||||
case ActivityTypeEnum.lemmaMeaning:
|
||||
case ActivityTypeEnum.grammarCategory:
|
||||
case ActivityTypeEnum.grammarError:
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
|
@ -68,6 +70,9 @@ enum ActivityTypeEnum {
|
|||
case 'grammar_category':
|
||||
case 'grammarCategory':
|
||||
return ActivityTypeEnum.grammarCategory;
|
||||
case 'grammar_error':
|
||||
case 'grammarError':
|
||||
return ActivityTypeEnum.grammarError;
|
||||
default:
|
||||
throw Exception('Unknown activity type: $split');
|
||||
}
|
||||
|
|
@ -128,6 +133,11 @@ enum ActivityTypeEnum {
|
|||
ConstructUseTypeEnum.corGC,
|
||||
ConstructUseTypeEnum.incGC,
|
||||
];
|
||||
case ActivityTypeEnum.grammarError:
|
||||
return [
|
||||
ConstructUseTypeEnum.corGE,
|
||||
ConstructUseTypeEnum.incGE,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -153,6 +163,8 @@ enum ActivityTypeEnum {
|
|||
return ConstructUseTypeEnum.corLM;
|
||||
case ActivityTypeEnum.grammarCategory:
|
||||
return ConstructUseTypeEnum.corGC;
|
||||
case ActivityTypeEnum.grammarError:
|
||||
return ConstructUseTypeEnum.corGE;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -178,6 +190,8 @@ enum ActivityTypeEnum {
|
|||
return ConstructUseTypeEnum.incLM;
|
||||
case ActivityTypeEnum.grammarCategory:
|
||||
return ConstructUseTypeEnum.incGC;
|
||||
case ActivityTypeEnum.grammarError:
|
||||
return ConstructUseTypeEnum.incGE;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -198,6 +212,7 @@ enum ActivityTypeEnum {
|
|||
return Icons.format_shapes;
|
||||
case ActivityTypeEnum.messageMeaning:
|
||||
case ActivityTypeEnum.grammarCategory:
|
||||
case ActivityTypeEnum.grammarError:
|
||||
return Icons.star; // TODO: Add to L10n
|
||||
}
|
||||
}
|
||||
|
|
@ -217,6 +232,7 @@ enum ActivityTypeEnum {
|
|||
case ActivityTypeEnum.lemmaMeaning:
|
||||
case ActivityTypeEnum.lemmaAudio:
|
||||
case ActivityTypeEnum.grammarCategory:
|
||||
case ActivityTypeEnum.grammarError:
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
|
@ -235,6 +251,7 @@ enum ActivityTypeEnum {
|
|||
|
||||
static List<ActivityTypeEnum> get _grammarPracticeTypes => [
|
||||
ActivityTypeEnum.grammarCategory,
|
||||
ActivityTypeEnum.grammarError,
|
||||
];
|
||||
|
||||
static List<ActivityTypeEnum> analyticsPracticeTypes(
|
||||
|
|
|
|||
|
|
@ -1,5 +1,11 @@
|
|||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:sentry_flutter/sentry_flutter.dart';
|
||||
|
||||
import 'package:fluffychat/l10n/l10n.dart';
|
||||
import 'package:fluffychat/pangea/choreographer/choreo_record_model.dart';
|
||||
import 'package:fluffychat/pangea/morphs/morph_features_enum.dart';
|
||||
import 'package:fluffychat/pangea/practice_activities/activity_type_enum.dart';
|
||||
import 'package:fluffychat/pangea/practice_activities/practice_activity_model.dart';
|
||||
import 'package:fluffychat/pangea/practice_activities/practice_target.dart';
|
||||
|
||||
|
|
@ -35,23 +41,67 @@ class ActivityQualityFeedback {
|
|||
}
|
||||
}
|
||||
|
||||
class GrammarErrorRequestInfo {
|
||||
final ChoreoRecordModel choreo;
|
||||
final int stepIndex;
|
||||
final String eventID;
|
||||
|
||||
const GrammarErrorRequestInfo({
|
||||
required this.choreo,
|
||||
required this.stepIndex,
|
||||
required this.eventID,
|
||||
});
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
return {
|
||||
'choreo': choreo.toJson(),
|
||||
'step_index': stepIndex,
|
||||
'event_id': eventID,
|
||||
};
|
||||
}
|
||||
|
||||
factory GrammarErrorRequestInfo.fromJson(Map<String, dynamic> json) {
|
||||
return GrammarErrorRequestInfo(
|
||||
choreo: ChoreoRecordModel.fromJson(json['choreo']),
|
||||
stepIndex: json['step_index'] as int,
|
||||
eventID: json['event_id'] as String,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class MessageActivityRequest {
|
||||
final String userL1;
|
||||
final String userL2;
|
||||
final PracticeTarget target;
|
||||
final ActivityQualityFeedback? activityQualityFeedback;
|
||||
final GrammarErrorRequestInfo? grammarErrorInfo;
|
||||
|
||||
MessageActivityRequest({
|
||||
required this.userL1,
|
||||
required this.userL2,
|
||||
required this.activityQualityFeedback,
|
||||
required this.target,
|
||||
this.grammarErrorInfo,
|
||||
}) {
|
||||
if (target.tokens.isEmpty) {
|
||||
throw Exception('Target tokens must not be empty');
|
||||
}
|
||||
}
|
||||
|
||||
String promptText(BuildContext context) {
|
||||
switch (target.activityType) {
|
||||
case ActivityTypeEnum.grammarCategory:
|
||||
return L10n.of(context).whatIsTheMorphTag(
|
||||
target.morphFeature!.getDisplayCopy(context),
|
||||
target.tokens.first.text.content,
|
||||
);
|
||||
case ActivityTypeEnum.grammarError:
|
||||
return L10n.of(context).fillInBlank;
|
||||
default:
|
||||
return target.tokens.first.vocabConstructID.lemma;
|
||||
}
|
||||
}
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
return {
|
||||
'user_l1': userL1,
|
||||
|
|
@ -60,6 +110,7 @@ class MessageActivityRequest {
|
|||
'target_tokens': target.tokens.map((e) => e.toJson()).toList(),
|
||||
'target_type': target.activityType.name,
|
||||
'target_morph_feature': target.morphFeature,
|
||||
'grammar_error_info': grammarErrorInfo?.toJson(),
|
||||
};
|
||||
}
|
||||
|
||||
|
|
@ -72,7 +123,8 @@ class MessageActivityRequest {
|
|||
other.userL2 == userL2 &&
|
||||
other.target == target &&
|
||||
other.activityQualityFeedback?.feedbackText ==
|
||||
activityQualityFeedback?.feedbackText;
|
||||
activityQualityFeedback?.feedbackText &&
|
||||
other.grammarErrorInfo == grammarErrorInfo;
|
||||
}
|
||||
|
||||
@override
|
||||
|
|
@ -80,7 +132,8 @@ class MessageActivityRequest {
|
|||
return activityQualityFeedback.hashCode ^
|
||||
target.hashCode ^
|
||||
userL1.hashCode ^
|
||||
userL2.hashCode;
|
||||
userL2.hashCode ^
|
||||
grammarErrorInfo.hashCode;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -48,6 +48,8 @@ sealed class PracticeActivityModel {
|
|||
return ActivityTypeEnum.morphId;
|
||||
case WordListeningPracticeActivityModel():
|
||||
return ActivityTypeEnum.wordFocusListening;
|
||||
case GrammarErrorPracticeActivityModel():
|
||||
return ActivityTypeEnum.grammarError;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -350,6 +352,24 @@ class LemmaPracticeActivityModel extends MultipleChoicePracticeActivityModel {
|
|||
});
|
||||
}
|
||||
|
||||
class GrammarErrorPracticeActivityModel
|
||||
extends MultipleChoicePracticeActivityModel {
|
||||
final String text;
|
||||
final int errorOffset;
|
||||
final int errorLength;
|
||||
final String eventID;
|
||||
|
||||
GrammarErrorPracticeActivityModel({
|
||||
required super.tokens,
|
||||
required super.langCode,
|
||||
required super.multipleChoiceContent,
|
||||
required this.text,
|
||||
required this.errorOffset,
|
||||
required this.errorLength,
|
||||
required this.eventID,
|
||||
});
|
||||
}
|
||||
|
||||
class EmojiPracticeActivityModel extends MatchPracticeActivityModel {
|
||||
EmojiPracticeActivityModel({
|
||||
required super.tokens,
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ import 'package:async/async.dart';
|
|||
import 'package:get_storage/get_storage.dart';
|
||||
import 'package:http/http.dart';
|
||||
|
||||
import 'package:fluffychat/pangea/analytics_practice/grammar_error_practice_generator.dart';
|
||||
import 'package:fluffychat/pangea/analytics_practice/morph_category_activity_generator.dart';
|
||||
import 'package:fluffychat/pangea/analytics_practice/vocab_audio_activity_generator.dart';
|
||||
import 'package:fluffychat/pangea/analytics_practice/vocab_meaning_activity_generator.dart';
|
||||
|
|
@ -128,6 +129,12 @@ class PracticeRepo {
|
|||
return VocabAudioActivityGenerator.get(req);
|
||||
case ActivityTypeEnum.grammarCategory:
|
||||
return MorphCategoryActivityGenerator.get(req);
|
||||
case ActivityTypeEnum.grammarError:
|
||||
assert(
|
||||
req.grammarErrorInfo != null,
|
||||
'Grammar error info must be provided for grammar error activities',
|
||||
);
|
||||
return GrammarErrorPracticeGenerator.get(req);
|
||||
case ActivityTypeEnum.morphId:
|
||||
return MorphActivityGenerator.get(req);
|
||||
case ActivityTypeEnum.wordMeaning:
|
||||
|
|
|
|||
|
|
@ -1,9 +1,7 @@
|
|||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:collection/collection.dart';
|
||||
|
||||
import 'package:fluffychat/l10n/l10n.dart';
|
||||
import 'package:fluffychat/pangea/constructs/construct_identifier.dart';
|
||||
import 'package:fluffychat/pangea/events/models/pangea_token_model.dart';
|
||||
import 'package:fluffychat/pangea/morphs/morph_features_enum.dart';
|
||||
|
|
@ -85,18 +83,6 @@ class PracticeTarget {
|
|||
(morphFeature?.name ?? "");
|
||||
}
|
||||
|
||||
String promptText(BuildContext context) {
|
||||
switch (activityType) {
|
||||
case ActivityTypeEnum.grammarCategory:
|
||||
return L10n.of(context).whatIsTheMorphTag(
|
||||
morphFeature!.getDisplayCopy(context),
|
||||
tokens.first.text.content,
|
||||
);
|
||||
default:
|
||||
return tokens.first.vocabConstructID.lemma;
|
||||
}
|
||||
}
|
||||
|
||||
ConstructIdentifier targetTokenConstructID(PangeaToken token) {
|
||||
final defaultID = token.vocabConstructID;
|
||||
final ConstructIdentifier? cId = morphFeature == null
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ class TokenFeedbackUtil {
|
|||
required TokenInfoFeedbackRequestData requestData,
|
||||
required String langCode,
|
||||
PangeaMessageEvent? event,
|
||||
VoidCallback? onUpdated,
|
||||
}) async {
|
||||
final resp = await showDialog(
|
||||
context: context,
|
||||
|
|
@ -23,6 +24,8 @@ class TokenFeedbackUtil {
|
|||
);
|
||||
|
||||
if (resp == null) return;
|
||||
|
||||
onUpdated?.call();
|
||||
await showDialog(
|
||||
context: context,
|
||||
builder: (context) {
|
||||
|
|
|
|||
|
|
@ -10,7 +10,6 @@ import 'package:fluffychat/l10n/l10n.dart';
|
|||
import 'package:fluffychat/pages/chat/chat.dart';
|
||||
import 'package:fluffychat/pages/chat/events/message_content.dart';
|
||||
import 'package:fluffychat/pages/chat/events/reply_content.dart';
|
||||
import 'package:fluffychat/pangea/chat/widgets/request_regeneration_button.dart';
|
||||
import 'package:fluffychat/pangea/common/utils/async_state.dart';
|
||||
import 'package:fluffychat/pangea/common/widgets/error_indicator.dart';
|
||||
import 'package:fluffychat/pangea/events/extensions/pangea_event_extension.dart';
|
||||
|
|
@ -258,11 +257,6 @@ class OverlayMessage extends StatelessWidget {
|
|||
),
|
||||
],
|
||||
),
|
||||
)
|
||||
else if (canRefresh)
|
||||
RequestRegenerationButton(
|
||||
textColor: textColor,
|
||||
onPressed: () => controller.requestRegeneration(event.eventId),
|
||||
),
|
||||
],
|
||||
),
|
||||
|
|
|
|||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Reference in a new issue