Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions docs/features/ukrainian-localization.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,12 @@ All user-facing Telegram bot text was localized to Ukrainian, and English contro
- Conversation control words were localized:
- `Skip` -> `Пропустити`
- `clear` -> `очистити`
- Follow-up copy simplifications:
- Main admin action label changed from `Завантажити акорди` to `Завантажити гармонію`.
- Add-song source prompt changed to `Джерело? (Посилання на оригінал)`.
- Add-song key prompt changed to `Оригінальна тональність?`.
- Add-song flow no longer prompts for arrangement notes.
- Chart upload flow no longer prompts for optional source URL before chart key.

## Service-layer localization

Expand Down
48 changes: 4 additions & 44 deletions src/handlers/charts.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@

from io import BytesIO
from typing import cast
from urllib.parse import urlparse

from telegram import InputFile, ReplyKeyboardRemove, Update
from telegram.ext import (
Expand All @@ -20,7 +19,7 @@
from services.song_service import SongNotFoundError
from storage.chart_storage import ChartStorageError

UPLOAD_MEDIA, UPLOAD_SOURCE_URL, UPLOAD_CHART_KEY = range(3)
UPLOAD_MEDIA, UPLOAD_CHART_KEY = range(2)
UPLOAD_CHART_STATE_KEY = "upload_chart_state"


Expand Down Expand Up @@ -124,7 +123,7 @@ async def upload_chart_media(update: Update, context: ContextTypes.DEFAULT_TYPE)
song_id = state.get("song_id")
if not isinstance(song_id, int):
await update.effective_message.reply_text(
"Стан завантаження втрачено. Почніть знову через «Завантажити акорди».",
"Стан завантаження втрачено. Почніть знову через «Завантажити гармонію».",
reply_markup=home_menu_markup(update, context) or ReplyKeyboardRemove(),
)
return ConversationHandler.END
Expand Down Expand Up @@ -153,30 +152,6 @@ async def upload_chart_media(update: Update, context: ContextTypes.DEFAULT_TYPE)
state["content"] = content
state["content_type"] = content_type
state["filename"] = filename
await update.effective_message.reply_text(
"Джерело (URL) необов'язкове. Надішліть http(s)-посилання або «Пропустити».",
reply_markup=cancel_markup(update),
)
return UPLOAD_SOURCE_URL


async def upload_chart_source_url(update: Update, context: ContextTypes.DEFAULT_TYPE) -> int:
if update.effective_message is None:
return ConversationHandler.END

text = (update.effective_message.text or "").strip()
if text.lower() == BUTTON_SKIP.lower():
source_url: str | None = None
else:
if not _looks_like_http_url(text):
await update.effective_message.reply_text(
"Посилання має починатися з http:// або https://, або надішліть «Пропустити»."
)
return UPLOAD_SOURCE_URL
source_url = text

state = _upload_state(context)
state["source_url"] = source_url
await update.effective_message.reply_text(
"Тональність акордів необов'язкова. Надішліть текст або «Пропустити».",
reply_markup=cancel_markup(update),
Expand All @@ -194,15 +169,14 @@ async def upload_chart_chart_key(update: Update, context: ContextTypes.DEFAULT_T
content = state.get("content")
content_type = state.get("content_type")
filename = state.get("filename")
source_url = state.get("source_url")
if (
not isinstance(song_id, int)
or not isinstance(content, bytes)
or not isinstance(content_type, str)
or not isinstance(filename, str)
):
await update.effective_message.reply_text(
"Стан завантаження втрачено. Почніть знову через «Завантажити акорди».",
"Стан завантаження втрачено. Почніть знову через «Завантажити гармонію».",
reply_markup=home_menu_markup(update, context) or ReplyKeyboardRemove(),
)
return ConversationHandler.END
Expand All @@ -216,7 +190,7 @@ async def upload_chart_chart_key(update: Update, context: ContextTypes.DEFAULT_T
original_filename=filename,
content_type=content_type,
content=content,
source_url=source_url if isinstance(source_url, str) else None,
source_url=None,
chart_key=chart_key,
),
)
Expand Down Expand Up @@ -253,15 +227,6 @@ def build_upload_chart_handler() -> ConversationHandler:
upload_chart_media,
)
],
UPLOAD_SOURCE_URL: [
MessageHandler(
filters.TEXT
& ~filters.COMMAND
& ~filters.Regex(CANCEL_BUTTON_PATTERN)
& filters.UpdateType.MESSAGE,
upload_chart_source_url,
)
],
UPLOAD_CHART_KEY: [
MessageHandler(
filters.TEXT
Expand Down Expand Up @@ -294,11 +259,6 @@ def _parse_song_id(args: list[str]) -> int | None:
return None


def _looks_like_http_url(value: str) -> bool:
parsed = urlparse(value)
return parsed.scheme in {"http", "https"} and bool(parsed.netloc)


def _song_id_from_callback(data: str | None, *, prefix: str) -> int | None:
if not isinstance(data, str):
return None
Expand Down
2 changes: 1 addition & 1 deletion src/handlers/navigation.py
Original file line number Diff line number Diff line change
Expand Up @@ -406,7 +406,7 @@ def _song_detail_keyboard(*, song_id: int, page: int, is_admin: bool) -> InlineK
]
)
rows.append(
[InlineKeyboardButton("Завантажити акорди", callback_data=f"upload:start:{song_id}")]
[InlineKeyboardButton("Завантажити гармонію", callback_data=f"upload:start:{song_id}")]
)
rows.append(
[InlineKeyboardButton("Назад до результатів", callback_data=f"browser:page:b:{page}")]
Expand Down
42 changes: 3 additions & 39 deletions src/handlers/repertoire.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,8 +43,7 @@
ADD_TEMPO,
ADD_TAGS,
ADD_NOTES,
ADD_ARRANGEMENT_NOTES,
) = range(10)
) = range(9)
EDIT_FIELD, EDIT_VALUE = range(2)
RESULT_MESSAGE_CHAR_LIMIT = 3500
CLEAR_INPUT = "очистити"
Expand All @@ -70,7 +69,6 @@ def format_song(song: Song) -> str:
tag_text = ", ".join(song.tags) if song.tags else "-"
tempo_text = str(song.tempo_bpm) if song.tempo_bpm is not None else "-"
notes_text = song.notes or "-"
arrangement_notes_text = song.arrangement_notes or "-"
return (
f"#{song.id} {song.title}\n"
f"Виконавець: {song.artist}\n"
Expand All @@ -81,7 +79,6 @@ def format_song(song: Song) -> str:
f"Темп: {tempo_text}\n"
f"Теги: {tag_text}\n"
f"Нотатки: {notes_text}\n"
f"Нотатки аранжування: {arrangement_notes_text}\n"
f"Статус: {_status_label(song.status)}"
)

Expand Down Expand Up @@ -311,14 +308,6 @@ def _parse_notes_update(raw_value: str) -> SongUpdate:
return SongUpdate(notes=raw_value)


def _parse_arrangement_notes_update(raw_value: str) -> SongUpdate:
if _is_clear(raw_value):
return SongUpdate(arrangement_notes=None)
if not raw_value:
raise ValueError("Нотатки аранжування мають бути текстом або «очистити».")
return SongUpdate(arrangement_notes=raw_value)


def _is_clear(raw_value: str) -> bool:
return raw_value.lower() == CLEAR_INPUT

Expand Down Expand Up @@ -347,10 +336,6 @@ def _format_notes(song: Song) -> str:
return song.notes or "-"


def _format_arrangement_notes(song: Song) -> str:
return song.arrangement_notes or "-"


EDIT_FIELD_SPECS: dict[str, EditFieldSpec] = {
"title": EditFieldSpec(
label="назва",
Expand Down Expand Up @@ -415,13 +400,6 @@ def _format_arrangement_notes(song: Song) -> str:
parse_input=_parse_notes_update,
aliases=("нотатки",),
),
"arrangement_notes": EditFieldSpec(
label="нотатки аранжування",
prompt="Нові нотатки аранжування? Надішліть текст або «очистити».",
format_current=_format_arrangement_notes,
parse_input=_parse_arrangement_notes_update,
aliases=("нотатки аранжування",),
),
}


Expand Down Expand Up @@ -596,7 +574,7 @@ async def add_song_artist(update: Update, context: ContextTypes.DEFAULT_TYPE) ->
payload["artist"] = _message_text(update)
if update.effective_message is not None:
await update.effective_message.reply_text(
"Джерело? Надішліть текст або «Пропустити».",
"Джерело? (Посилання на оригінал)",
reply_markup=skip_cancel_markup(update),
)
return ADD_SOURCE
Expand All @@ -608,7 +586,7 @@ async def add_song_source(update: Update, context: ContextTypes.DEFAULT_TYPE) ->
payload["source_url"] = None if text.lower() == BUTTON_SKIP.lower() else text
if update.effective_message is not None:
await update.effective_message.reply_text(
"Тональність?",
"Оригінальна тональність?",
reply_markup=cancel_markup(update),
)
return ADD_KEY
Expand Down Expand Up @@ -702,18 +680,6 @@ async def add_song_notes(update: Update, context: ContextTypes.DEFAULT_TYPE) ->
payload = _pending_song(context)
text = _message_text(update)
payload["notes"] = None if text.lower() == BUTTON_SKIP.lower() else text
if update.effective_message is not None:
await update.effective_message.reply_text(
"Нотатки аранжування? Надішліть текст або «Пропустити».",
reply_markup=skip_cancel_markup(update),
)
return ADD_ARRANGEMENT_NOTES


async def add_song_arrangement_notes(update: Update, context: ContextTypes.DEFAULT_TYPE) -> int:
payload = _pending_song(context)
text = _message_text(update)
arrangement_notes = None if text.lower() == BUTTON_SKIP.lower() else text
service = get_song_service(context)
try:
song = await service.create_song(
Expand All @@ -727,7 +693,6 @@ async def add_song_arrangement_notes(update: Update, context: ContextTypes.DEFAU
tempo_bpm=cast(int | None, payload.get("tempo_bpm")),
tags=cast(list[str], payload.get("tags", [])),
notes=cast(str | None, payload.get("notes")),
arrangement_notes=arrangement_notes,
)
)
except ValueError as error:
Expand Down Expand Up @@ -966,7 +931,6 @@ def build_add_song_handler() -> ConversationHandler:
ADD_TEMPO: [_text_step(add_song_tempo)],
ADD_TAGS: [_text_step(add_song_tags)],
ADD_NOTES: [_text_step(add_song_notes)],
ADD_ARRANGEMENT_NOTES: [_text_step(add_song_arrangement_notes)],
},
fallbacks=_conversation_fallbacks(),
name="add_song",
Expand Down
2 changes: 1 addition & 1 deletion src/handlers/ui.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
MENU_TAGS = "🏷️ Теги"
MENU_HELP = "❓ Допомога"
MENU_ADD_SONG = "➕ Додати пісню"
MENU_UPLOAD_CHART = "🖼️ Завантажити акорди"
MENU_UPLOAD_CHART = "🖼️ Завантажити гармонію"
MENU_BACKUP = "💾 Резервна копія"

BUTTON_SKIP = "Пропустити"
Expand Down
Loading
Loading