Spaces:
Running on Zero
Running on Zero
Commit ยท
663fdd6
1
Parent(s): 9318723
natural amharic
Browse files- app/bot/handlers/errors.py +1 -1
- app/bot/keyboards/library.py +2 -8
- app/core/labels.py +12 -0
- locales/am.json +48 -48
- tests/test_logic.py +19 -3
app/bot/handlers/errors.py
CHANGED
|
@@ -9,7 +9,7 @@ from app.config import get_settings
|
|
| 9 |
|
| 10 |
log = logging.getLogger(__name__)
|
| 11 |
_last_notified: dict[str, float] = {}
|
| 12 |
-
NOTICE = "โ ๏ธ แฝแแญ แฐแ
|
| 13 |
|
| 14 |
|
| 15 |
async def on_error(event: ErrorEvent, bot: Bot) -> bool:
|
|
|
|
| 9 |
|
| 10 |
log = logging.getLogger(__name__)
|
| 11 |
_last_notified: dict[str, float] = {}
|
| 12 |
+
NOTICE = "โ ๏ธ แแญแฃ แฝแแญ แฐแแ แจแข แฅแฃแซแฝแ แฅแแฐแแ แแญแฉแข\nSomething went wrong, please try again."
|
| 13 |
|
| 14 |
|
| 15 |
async def on_error(event: ErrorEvent, bot: Bot) -> bool:
|
app/bot/keyboards/library.py
CHANGED
|
@@ -5,18 +5,12 @@ from aiogram.types import InlineKeyboardButton, InlineKeyboardMarkup
|
|
| 5 |
from app.core.catalog import KINDS
|
| 6 |
from app.core.i18n import t
|
| 7 |
from app.db.models import Course, Material
|
|
|
|
| 8 |
from app.services.classifier import display_title
|
| 9 |
|
| 10 |
|
| 11 |
-
def clip(text: str, n: int = 48) -> str:
|
| 12 |
-
return text if len(text) <= n else text[: n - 1] + "โฆ"
|
| 13 |
-
|
| 14 |
-
|
| 15 |
def course_label(c: Course) -> str:
|
| 16 |
-
|
| 17 |
-
if c.code and len(c.code) + 3 + len(c.name) <= 36:
|
| 18 |
-
return f"{c.code} ยท {c.name}"
|
| 19 |
-
return clip(c.name, 36)
|
| 20 |
|
| 21 |
|
| 22 |
def course_icon(c: Course) -> str:
|
|
|
|
| 5 |
from app.core.catalog import KINDS
|
| 6 |
from app.core.i18n import t
|
| 7 |
from app.db.models import Course, Material
|
| 8 |
+
from app.core.labels import clip, course_label as _course_label
|
| 9 |
from app.services.classifier import display_title
|
| 10 |
|
| 11 |
|
|
|
|
|
|
|
|
|
|
|
|
|
| 12 |
def course_label(c: Course) -> str:
|
| 13 |
+
return _course_label(c.code, c.name)
|
|
|
|
|
|
|
|
|
|
| 14 |
|
| 15 |
|
| 16 |
def course_icon(c: Course) -> str:
|
app/core/labels.py
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Short labels for buttons (pure, so they can be tested without Telegram)."""
|
| 2 |
+
|
| 3 |
+
|
| 4 |
+
def clip(text: str, n: int = 48) -> str:
|
| 5 |
+
return text if len(text) <= n else text[: n - 1] + "โฆ"
|
| 6 |
+
|
| 7 |
+
|
| 8 |
+
def course_label(code, name: str) -> str:
|
| 9 |
+
"""One button line: "code ยท name" when it fits, else just the name."""
|
| 10 |
+
if code and len(code) + 3 + len(name) <= 36:
|
| 11 |
+
return f"{code} ยท {name}"
|
| 12 |
+
return clip(name, 36)
|
locales/am.json
CHANGED
|
@@ -1,66 +1,66 @@
|
|
| 1 |
{
|
| 2 |
-
"welcome": "๐ แฐแแ <b>{name}</b>!\nแแฐ <b>DBU Central</b> แฅแแณแ
|
| 3 |
-
"gate_text": "แฆแฑแ แแแ แแ
|
| 4 |
-
"gate_join_btn": "๐ข แปแแแ แฐแแแ
|
| 5 |
"gate_check_btn": "โ
แฐแแแ
แซแแ",
|
| 6 |
-
"gate_not_yet": "แแ
|
| 7 |
-
"choose_stream": "แจแตแแ
|
| 8 |
"stream_natural": "๐ฌ แฐแแฅแฎ แณแญแแต",
|
| 9 |
"stream_social": "๐ แแ
แ แซแ แณแญแแต",
|
| 10 |
-
"choose_year": "
|
| 11 |
"year_label": "{n}แ แแแต",
|
| 12 |
-
"onboarded": "โ
แฐแแแ
|
| 13 |
-
"menu_title": "
|
| 14 |
-
"btn_library": "๐
|
| 15 |
"btn_gpa": "๐งฎ GPA แแตแซ",
|
| 16 |
-
"btn_invite": "๐ฅ แแฐแ แแฅ
|
| 17 |
"btn_lang": "๐ แแแ",
|
| 18 |
-
"soon": "๐ง แญ
|
| 19 |
-
"invite_text": "๐
|
| 20 |
-
"referral_reward": "๐ฅ แ
|
| 21 |
-
"lang_changed": "โ
|
| 22 |
-
"banned": "โ
|
| 23 |
-
"btn_profile": "๐ค แแฎแแญ
|
| 24 |
"btn_top": "๐ แฐแจแ",
|
| 25 |
"btn_back": "โ๏ธ แฐแแแต",
|
| 26 |
"btn_prev": "โฌ
๏ธ",
|
| 27 |
"btn_next": "โก๏ธ",
|
| 28 |
-
"kind_slide": "๐ แตแแญ
|
| 29 |
-
"kind_exam": "๐
|
| 30 |
-
"kind_note": "๐
|
| 31 |
-
"kind_book": "๐ แ
|
| 32 |
-
"lib_courses": "๐ <b>แฎแญแถแฝ</b>\nแฎแญแต
|
| 33 |
-
"lib_no_courses": "แ
|
| 34 |
-
"lib_kinds": "๐ <b>{course}</b>\nแแ
|
| 35 |
-
"lib_no_materials": "แแแ
แฎแญแต แแ
|
| 36 |
"lib_materials": "๐ <b>{course}</b> ยท {kind}",
|
| 37 |
-
"file_missing": "แญ
|
| 38 |
-
"search_none": "๐ แแแ แ
|
| 39 |
-
"search_results": "๐ <b>
|
| 40 |
-
"profile_text": "๐ค <b>{name}</b>\n๐ {stream} ยท {year}\n\n๐
แฐแจแ {level}
|
| 41 |
-
"top_text": "๐ <b>แจแณแแแฑ แฐแจแ</b> ยท
|
| 42 |
-
"inline_open_bot": "๐ แฆแฑแ แญแ
|
| 43 |
-
"prize_block": "\n\n๐ฐ <b>แณแแ
|
| 44 |
-
"to_next": "แแแฃแฉ แฐแจแ {n} XP
|
| 45 |
-
"max_level": "แจแแฐแ แฐแจแ
|
| 46 |
-
"levelup": "๐ แฐแจแ {level} แฐแญ
|
| 47 |
-
"streak_bonus": "๐ฅ {streak} แแ
|
| 48 |
-
"referral_qualified": "๐ แจแแ
|
| 49 |
-
"season_win": "๐
แฃแแแ แณแแแต #{rank} แแฅ
|
| 50 |
-
"season_win_prize": "\n๐ฐ แฝแแ
|
| 51 |
-
"season_raffle": "๐ แ แณแแแณแ แฅแฃ แ แธแ
|
| 52 |
-
"rules_text": "๐ <b>แ
แแฝ</b>\n\n<b>XP แฅแแดแต แญแแแ</b>\nโข แ แจแแ
|
| 53 |
-
"level_1": "แ แฒแต
|
| 54 |
"level_2": "แแแช",
|
| 55 |
-
"level_3": "แแ แ",
|
| 56 |
-
"level_4": "แตแ
|
| 57 |
-
"level_5": "แฅแ
|
| 58 |
"level_6": "แแแญ",
|
| 59 |
"level_7": "แ แแซแช",
|
| 60 |
"level_8": "แฎแจแฅ",
|
| 61 |
-
"level_9": "
|
| 62 |
"level_10": "แแ
",
|
| 63 |
-
"gate_missing": "แแ แซแแฐแแแ
|
| 64 |
-
"gate_join_one": "๐ข {name} แฐแแแ
|
| 65 |
-
"gate_not_yet_list": "แแ แ แแฐแแแ
|
| 66 |
}
|
|
|
|
| 1 |
{
|
| 2 |
+
"welcome": "๐ แฐแแ <b>{name}</b>!\nแแฐ <b>DBU Central</b> แฅแแณแ แฐแ
แ แแฃแฝแ ๐",
|
| 3 |
+
"gate_text": "แฆแฑแ แแแ แแ แปแแแฝแแแ แจแแญแญแต แแฉแแฝแแ แแแแแ แซแตแแแแ ๐\nแจแ แ แแ ยซโ
แฐแแแ
แซแแยป แแฉแข",
|
| 4 |
+
"gate_join_btn": "๐ข แปแแแ แฐแแแแ",
|
| 5 |
"gate_check_btn": "โ
แฐแแแ
แซแแ",
|
| 6 |
+
"gate_not_yet": "แแ แ แแฐแแแแแฝแแแค แแแแชแซ แฐแแแแแข",
|
| 7 |
+
"choose_stream": "แแญแแฝแ แจแตแแ แแ? ๐",
|
| 8 |
"stream_natural": "๐ฌ แฐแแฅแฎ แณแญแแต",
|
| 9 |
"stream_social": "๐ แแ
แ แซแ แณแญแแต",
|
| 10 |
+
"choose_year": "แตแแฐแ แแแต แฐแแช แแฝแ?",
|
| 11 |
"year_label": "{n}แ แแแต",
|
| 12 |
+
"onboarded": "โ
แฐแแแแฃแฝแแ! แจแณแฝ แซแแ แแ แจแแแแฝแแตแ แแจแก ๐",
|
| 13 |
+
"menu_title": "แแ แแแแฝแ? ๐",
|
| 14 |
+
"btn_library": "๐ แแดแชแซแแฝ",
|
| 15 |
"btn_gpa": "๐งฎ GPA แแตแซ",
|
| 16 |
+
"btn_invite": "๐ฅ แแฐแ แแฅแ",
|
| 17 |
"btn_lang": "๐ แแแ",
|
| 18 |
+
"soon": "๐ง แญแ แแ แฅแจแฐแ แซ แแแฃ แ แ
แญแก แญแฐแญแณแฝแแ!",
|
| 19 |
+
"invite_text": "๐ แแฐแแปแฝแแ แจแแตแแฅแแ แต แแแญแฆ\n{link}\n\n๐ฅ แจแแ แแฝแแตแฆ <b>{count}</b> ยท แ แฐแแฅ แฅแจแฐแ แแ แซแแตแฆ <b>{qualified}</b>\nโญ XPแฆ <b>{points}</b>\n\nแแฐแแฝแ แขแซแแต แ3 แแ แฆแฑแ แจแฐแ แแ แตแแ แแญแป แญแฐแฃแฝแแ ๐",
|
| 20 |
+
"referral_reward": "๐ฅ แ แแต แแฐแแฝแ แ แแแซแฝแ แแฅแทแ! +{points} XP ๐\nแแฐแแฝแ แฆแฑแ แแ แแ แฒแแฅแ แฐแจแแช แตแแ แแแฝแแข",
|
| 21 |
+
"lang_changed": "โ
แฅแบแฃ แ แแ แ แ แแญแ แแ!",
|
| 22 |
+
"banned": "โ แ แซแแแณแฝแ แณแแทแแข",
|
| 23 |
+
"btn_profile": "๐ค แแฎแแญแ",
|
| 24 |
"btn_top": "๐ แฐแจแ",
|
| 25 |
"btn_back": "โ๏ธ แฐแแแต",
|
| 26 |
"btn_prev": "โฌ
๏ธ",
|
| 27 |
"btn_next": "โก๏ธ",
|
| 28 |
+
"kind_slide": "๐ แตแแญแถแฝ",
|
| 29 |
+
"kind_exam": "๐ แจแตแฎ แแฐแแแฝ",
|
| 30 |
+
"kind_note": "๐ แแต / แแแ",
|
| 31 |
+
"kind_book": "๐ แแปแแแต",
|
| 32 |
+
"lib_courses": "๐ <b>แฎแญแถแฝ</b>\nแฎแญแต แแจแกแฃ แแญแ แจแฎแญแฑแ แตแ แฝแแฝแ แแแ ๐",
|
| 33 |
+
"lib_no_courses": "แแแญแแฝแแ แแแแณแฝแ แแ แแดแชแซแ แ แแแฃแแข\nแจแฎแญแฑแ แตแ แฝแแฝแ แแแ ๐",
|
| 34 |
+
"lib_kinds": "๐ <b>{course}</b>\nแแ แแแญแแฝแ?",
|
| 35 |
+
"lib_no_materials": "แแแ
แฎแญแต แแ แแแ แแดแชแซแ แจแแแค แ แ
แญแก แฅแแจแแซแแ!",
|
| 36 |
"lib_materials": "๐ <b>{course}</b> ยท {kind}",
|
| 37 |
+
"file_missing": "แญแ แแญแ แ แแฐแแแ ๐",
|
| 38 |
+
"search_none": "๐ แแแ แ แแแแแแข แแ แแ แแญแฉแฃ แแแณแ แจแฎแญแฑแ แตแ แแญแ แฎแตแข",
|
| 39 |
+
"search_results": "๐ <b>แ ยซ{q}ยป แซแแแแต</b>",
|
| 40 |
+
"profile_text": "๐ค <b>{name}</b>\n๐ {stream} ยท {year}\n\n๐
แฐแจแ {level}แฆ <b>{title}</b>\nโญ แ แ
แแ XPแฆ <b>{points}</b> ({to_next})\n๐
แจแแ
แณแแแต XPแฆ <b>{week_xp}</b> ยท {week_rank}\n๐ฅ แฐแจแณแณแญ แแแตแฆ <b>{streak}</b> (แจแแฐแแ {best})\n๐ฅ แจแแ แแฝแแธแแฆ <b>{refs}</b> (แแ {refs_q})",
|
| 41 |
+
"top_text": "๐ <b>แจแณแแแฑ แฐแจแ</b> ยท {days} แแ แแญแทแ\n{rows}\n\n๐ แฅแแแฐแฆ <b>{rank}</b> ยท {week_xp} XP\nโ๏ธ {n_label}แฆ <b>{natural}</b> ยท {s_label}แฆ <b>{social}</b>{prize}\n\nโน๏ธ แ
แแนแฆ /rules",
|
| 42 |
+
"inline_open_bot": "๐ แฆแฑแ แญแแฑ",
|
| 43 |
+
"prize_block": "\n\n๐ฐ <b>แจแณแแแฑ แฝแแแต</b> (แจแแฃแญแ แซแญแต)แฆ\n{lines}\n๐ แฅแฃแฆ {raffle} แฅแญ (แแแฉแต แฅแ แฐแแณแณแชแแฝ)\nแแแแณแฐแญแฆ แ แณแแแฑ แขแซแแต {days} แแ แแแฃแตแ {xp} XP แแฐแฅแฐแฅ แซแตแแแแ",
|
| 44 |
+
"to_next": "แแแฃแฉ แฐแจแ {n} XP แแญแทแ",
|
| 45 |
+
"max_level": "แจแแฐแแ แฐแจแ แแญ แฐแญแณแฝแแ ๐",
|
| 46 |
+
"levelup": "๐ แฐแจแ {level} แแญ แฐแญแณแฝแแแฆ <b>{title}</b>!",
|
| 47 |
+
"streak_bonus": "๐ฅ {streak} แแ แณแณแแญแก แแฅแณแฝแแ! +{xp} XP แแญแป",
|
| 48 |
+
"referral_qualified": "๐ แจแแ แแฝแแต แแฐแ แฆแฑแ แ แฐแแฅ แฅแจแฐแ แแ แแ! +{xp} XP",
|
| 49 |
+
"season_win": "๐
แฃแแแ แณแแแต #{rank} แแฅแณแฝแแ ({xp} XP)! แฅแแณแ แฐแต แ แแฝแ ๐",
|
| 50 |
+
"season_win_prize": "\n๐ฐ แฝแแแณแฝแแฆ {birr} แฅแญ แจแแฃแญแ แซแญแตแข แ แตแแ แ Telegram แซแแแฝแแแข",
|
| 51 |
+
"season_raffle": "๐ แ แณแแแณแแ แฅแฃ แ แธแแ๏ฟฝ๏ฟฝ๏ฟฝแแ! ๐",
|
| 52 |
+
"rules_text": "๐ <b>แ
แแฝ</b>\n\n<b>XP แฅแแดแต แญแแแ?</b>\nโข แ แจแแ แฆแฑแ แตแตแจแแฑแฆ 3-7 XP (แแ แณแณแแญแก แตแตแแก แญแจแแซแ)\nโข แแญแ แตแณแแญแฑแฆ +1 (แ แแ แฅแตแจ 3)\nโข แแฐแ แตแตแแฅแแฆ แฒแแฃ +5แฃ แขแซแแต แ3 แแ แจแฐแ แแ +30 (แ แณแแแต แฅแตแจ 5 แแฐแ)\nโข แแแ แ
แ แ แตแฐแแฝแฆ แ แตแแ แแฉ XP แแฐแฅ แญแฝแแ\nโข แ แซแต-แฐแญ แจแแแ XP แ แแ แจ{cap} แ แญแ แแฅแ\n\n<b>แจแณแแแฑ แแตแตแญ</b>\nแฐแ แญแแแซแแฃ แฅแแต แญแแแ (แ แขแตแฎแตแซ แฐแแต)แข\nแฝแแแต แฒแแญ แแแแณแฐแญแฆ แ แณแแแฑ แขแซแแต {days} แแ แแแฃแตแฃ แขแซแแต {xp} XP แแแแตแฃ แปแแ แแตแฅ แแแจแตแฃ แฅแ แจแณแแแแต แแฅแฃแฝแ แคแแ แแแ แ แแ แตแข\n\n<b>แแตแแแแต</b>\nโข แฅแซแแณแแฑ XP แ แแญแแแฅ แแแแฅ แญแแแแฃแแค แ แแต แตแญแแต แแแต แแ แ แญแจแแแแข\nโข แ แ แซแฃแช แแแญ (แจแแธแต แ แซแแแตแฃ แซแแฐแแแฐ แฅแ แแฃแฅแแต) แ แตแแ แญแแจแแจแแแค แแฅแฅ แแแแต แแญแ แ แซแแแต แแณแแต แญแฝแแแข\nโข แฝแแแถแฝ แแแจแฉ แญแฝแแแค แจแ แตแแ แแณแ แจแแจแจแป แแแข\nโข แแแณแฐแ แแแ แญแแซ แ แซแตแแแแแข",
|
| 53 |
+
"level_1": "แ แฒแต แแข",
|
| 54 |
"level_2": "แแแช",
|
| 55 |
+
"level_3": "แแ แ แฐแแช",
|
| 56 |
+
"level_4": "แตแ",
|
| 57 |
+
"level_5": "แฅแแฅ",
|
| 58 |
"level_6": "แแแญ",
|
| 59 |
"level_7": "แ แแซแช",
|
| 60 |
"level_8": "แฎแจแฅ",
|
| 61 |
+
"level_9": "แปแแแฎแ",
|
| 62 |
"level_10": "แแ
",
|
| 63 |
+
"gate_missing": "แแ แซแแฐแแแแแฝแแตแฆ {list}",
|
| 64 |
+
"gate_join_one": "๐ข {name} แฐแแแแ",
|
| 65 |
+
"gate_not_yet_list": "แแ แ แแฐแแแแแฝแแแฆ {list}"
|
| 66 |
}
|
tests/test_logic.py
CHANGED
|
@@ -338,13 +338,29 @@ def test_display_title_cleans_old_titles_but_not_manual_ones():
|
|
| 338 |
|
| 339 |
|
| 340 |
def test_course_label_fits_one_line():
|
| 341 |
-
from app.
|
| 342 |
|
| 343 |
-
assert course_label(
|
| 344 |
-
|
|
|
|
| 345 |
assert len(long_) <= 36 and long_.startswith("Communicative English")
|
| 346 |
|
| 347 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 348 |
if __name__ == "__main__":
|
| 349 |
for name, fn in list(globals().items()):
|
| 350 |
if name.startswith("test_"):
|
|
|
|
| 338 |
|
| 339 |
|
| 340 |
def test_course_label_fits_one_line():
|
| 341 |
+
from app.core.labels import course_label
|
| 342 |
|
| 343 |
+
assert course_label("Math1003", "Calculus") == "Math1003 ยท Calculus"
|
| 344 |
+
assert course_label(None, "General Biology") == "General Biology"
|
| 345 |
+
long_ = course_label("FLEn1001", "Communicative English Language Skills I")
|
| 346 |
assert len(long_) <= 36 and long_.startswith("Communicative English")
|
| 347 |
|
| 348 |
|
| 349 |
+
def test_locales_stay_in_sync():
|
| 350 |
+
"""Every Amharic string must keep the same keys, {placeholders} and HTML tags as the English one,
|
| 351 |
+
otherwise the bot crashes (KeyError) the first time that message is sent."""
|
| 352 |
+
import json
|
| 353 |
+
import re
|
| 354 |
+
|
| 355 |
+
root = Path(__file__).resolve().parents[1] / "locales"
|
| 356 |
+
am = json.loads((root / "am.json").read_text(encoding="utf-8"))
|
| 357 |
+
en = json.loads((root / "en.json").read_text(encoding="utf-8"))
|
| 358 |
+
assert set(am) == set(en)
|
| 359 |
+
for key in en:
|
| 360 |
+
assert sorted(re.findall(r"\{(\w+)\}", am[key])) == sorted(re.findall(r"\{(\w+)\}", en[key])), key
|
| 361 |
+
assert sorted(re.findall(r"</?\w+>", am[key])) == sorted(re.findall(r"</?\w+>", en[key])), key
|
| 362 |
+
|
| 363 |
+
|
| 364 |
if __name__ == "__main__":
|
| 365 |
for name, fn in list(globals().items()):
|
| 366 |
if name.startswith("test_"):
|