// ┌──────────────────────────────────────────────────────────────────┐ // │ lang.rs — All user-facing messages live here. │ // │ Translate the bot by editing this single file. │ // │ Naming: L__ for simple &str; │ // │ fn fmt__(...) -> String for parameterized. │ // └──────────────────────────────────────────────────────────────────┘ // ── Error display prefixes (used in error.rs Display impl) ──────────── pub const L_ERR_DATABASE: &str = "Database error"; pub const L_ERR_HTTP: &str = "HTTP error"; pub const L_ERR_DISCORD: &str = "Discord error"; pub const L_ERR_VOICE_JOIN: &str = "Voice join error"; pub const L_ERR_VOICE_CONTROL: &str = "Voice control error"; pub const L_ERR_IO: &str = "IO error"; pub const L_ERR_CRON: &str = "Cron error"; pub const L_ERR_IMAGE: &str = "Image error"; pub const L_ERR_QR: &str = "QR error"; pub const L_ERR_SCRAPER: &str = "Scraper error"; pub const L_ERR_CONFIG: &str = "Config error"; pub const L_ERR_TASK_JOIN: &str = "Task join error"; // ── Common / shared error messages ──────────────────────────────────── /// "Nie jestem w gildii" — bot must be invoked inside a guild pub const L_NOT_IN_GUILD: &str = "Nie jestem w gildii"; /// "Nie jesteś na kanale głosowym" — user must be in a voice channel pub const L_NOT_IN_VOICE: &str = "Nie jesteś na kanale głosowym"; /// "Zły typ kanału" — wrong channel variant pub const L_BAD_CHANNEL_TYPE: &str = "Zły typ kanału"; /// "Grupa triggerów nie znaleziona" — trigger group not found pub const L_TRIGGER_NOT_FOUND: &str = "Grupa triggerów nie znaleziona"; // ── AI / senator command ────────────────────────────────────────────── pub const SENATOR_SYSTEM_PROMPT: &str = "Jesteś Senatorem Steavenem Sperma Armstrongiem. Jesteś rasistą, seksistą i homofobem. Odpowiadasz w języku polskim. Odpowiedź ma maksymalnie 100 słów. Nie używaj formatowania markdown. Nie używaj zawołań do narzędzi."; // ── Cron command ────────────────────────────────────────────────────── pub const L_CRON_QUEUED: &str = "Praca cron zakolejkowana"; pub const L_CRON_NONE: &str = "Brak zakolejkowanych prac cron"; pub const L_CRON_TITLE: &str = "Prace cron"; pub const L_CRON_ACTIVE: &str = "✅ Aktywna"; pub const L_CRON_PAUSED: &str = "⏸️ Zapauzowana"; pub fn fmt_cron_audio_queued(cron: &str) -> String { format!("Praca audio zakolejkowana z cron'em `{cron}`") } pub fn fmt_cron_label_audio(name: &str) -> String { format!("Audio: `{name}`") } pub fn fmt_cron_label_msg(msg: &str) -> String { format!("Wiadomość: `{msg}`") } pub fn fmt_cron_job_status(id: i64, status: &str) -> String { format!("ID {id} — {status}") } pub fn fmt_cron_field(cron: &str, chan: i64, desc: &str) -> String { format!("`{cron}` — <#{chan}>\n{desc}") } pub fn fmt_cron_deleted(id: i64) -> String { format!("Praca cron z ID `{id}` została usunięta") } pub fn fmt_cron_not_found(id: i64) -> String { format!("Praca z ID `{id}` nie została znaleziona") } pub fn fmt_cron_paused(id: i64) -> String { format!("Praca cron z ID `{id}` została zapauzowana") } pub fn fmt_cron_resumed(id: i64) -> String { format!("Praca cron z ID `{id}` została wznowiona") } // ── Math command ────────────────────────────────────────────────────── pub const L_MATH_TOO_LONG: &str = "Ekspresja za długa (max 1000 znaków"; pub const L_MATH_NOT_ASCII: &str = "Tylko znaki ASCII dozwolone"; pub const L_MATH_UNBALANCED: &str = "Nawiasy są niezbalansowane"; pub const L_MATH_DANGEROUS: &str = "Ekspresja zawiera groźne słowa"; pub const L_MATH_NO_RESULT: &str = "Brak rezultatu"; pub fn fmt_math_qalc_err(e: &dyn std::fmt::Display) -> String { format!("qalc nie jest dostępny: `{e}`") } pub fn fmt_math_result(r: &str) -> String { format!("```\n{r}\n```") } // ── Music command ───────────────────────────────────────────────────── pub const L_MUSIC_NEED_NAME: &str = "Daj mi nazwę pliku, URL albo plik"; pub const L_MUSIC_NO_AUDIO_ON_CP: &str = "Brak powiązanego audio na copyparty"; pub const L_MUSIC_NO_URL_IN_RESULT: &str = "Wynik nie ma URL"; pub const L_MUSIC_CANT_COMPRESS_LIVE: &str = "Nie można skompresować transmisji na żywo, odtwarzam normalnie"; pub const L_MUSIC_STOPPED: &str = "wyszed"; pub const L_VOICE_USER_NOT_IN_VC: &str = "You are not in a voice channel"; pub fn fmt_music_search_failed(e: &dyn std::fmt::Display) -> String { format!("Szukanie na copyparty zawiodło: `{e}`") } pub fn fmt_music_playing(name: &str) -> String { format!("Grajcowanie: `{name}`") } pub fn fmt_voice_joined(cid: &dyn std::fmt::Display) -> String { format!("Dołączon <#{cid}>") } pub fn fmt_voice_join_failed(e: &dyn std::fmt::Display) -> String { format!("Nie udało się połączyć z VC: `{e}`") } // ── TTS command ─────────────────────────────────────────────────────── pub const L_TTS_SAVED: &str = "TTS zapisany"; pub const L_TTS_PLAYING: &str = "Grom TTS na kanale głosowym"; // ── Trigger command ─────────────────────────────────────────────────── pub const L_TRIGGER_UPDATED: &str = "Trigger zaktualizowany"; pub const L_TRIGGER_KW_NONE: &str = "Brak"; pub fn fmt_trigger_created(name: &str) -> String { format!("Grupa trigger `{name}` utworzona") } pub fn fmt_trigger_failed(e: &dyn std::fmt::Display) -> String { format!("Nie udało się: `{e}`") } pub fn fmt_trigger_deleted(name: &str) -> String { format!("Grupa trigger `{name}` usunięta") } pub fn fmt_trigger_title(name: &str) -> String { format!("Trigger: `{name}`") } // ── Utility commands ────────────────────────────────────────────────── pub const L_UTIL_NEED_INPUT: &str = "Podaj plik z copyparty lub załącznik"; pub const L_UTIL_PICK_FROM_AUTOCOMPLETE: &str = "Wybierz plik z podpowiedzi"; pub const L_UTIL_DOWNLOAD_FAILED: &str = "Nie udało się pobrać pliku"; pub const L_UTIL_UNSUPPORTED_TYPE: &str = "Nieobsługiwany typ pliku"; pub const L_UTIL_COMPRESS_FAILED: &str = "Kompresja nie powiodła się"; pub const L_UTIL_CONVERT_FAILED: &str = "Konwersja nie powiodła się"; pub const L_UTIL_UPLOAD_FAILED: &str = "Nie udało się wysłać na serwer"; pub const L_UTIL_NO_ATTACHMENTS: &str = "Brak załączników w wiadomości"; pub const L_SEARCH_NO_RESULTS: &str = "No results found"; pub fn fmt_util_compressed(url: &str) -> String { format!("Skompresowano\n`{url}`") } pub fn fmt_util_converted(url: &str) -> String { format!("Skonwertowano\n`{url}`") } pub fn fmt_util_convert_err(stderr: &str) -> String { format!("Błąd konwersji: `{stderr}`") } pub fn fmt_uploaded(url: &str) -> String { format!("Wysłon: `{url}`") } pub fn fmt_download_uploaded(url: &str) -> String { format!("pobioron i wysyłon: `{url}`") } pub fn fmt_upload_summary(count: usize) -> String { format!("Wysłano `{count}` plików:\n") } pub fn fmt_upload_partial_failed(count: usize) -> String { format!("\n\nNie wysłano `{count}` plików") } pub fn fmt_message_compressed(made: usize, total: usize) -> String { format!("Skompresowano `{made}` z `{total}` plików:") } pub fn fmt_search_result(name: &str, url: &str) -> String { format!("**{name}**\n`{url}`") } pub fn fmt_download_failed(stderr: &str) -> String { format!( "Pobieranie się nie powiodło: `{}`", translate_ytdlp_stderr(stderr) ) } pub fn fmt_ytdlp_err(e: &dyn std::fmt::Display) -> String { format!("yt-dlp nie mógło się uruchomić: `{e}`") } pub fn fmt_ytdlp_stderr(stderr: &str) -> String { translate_ytdlp_stderr(stderr) } /// Translate yt-dlp's verbose stderr into a short, user-friendly Polish /// message. yt-dlp emits lots of `[youtube] Extracting URL…`, `WARNING:` /// and `ERROR:` noise that is meaningless to a normal Discord user; this /// recognises the handful of failure modes users actually hit and falls /// back to the first `ERROR:`/`WARNING:` line (stripped of the /// `ERROR: [extractor] id: ` prefix) when nothing matches, so we never /// show the full multi-line trace. pub fn translate_ytdlp_stderr(stderr: &str) -> String { let lower = stderr.to_ascii_lowercase(); // YouTube's anti-bot sign-in wall. yt-dlp can't bypass this without // browser cookies configured on the host — surface that to the user // rather than the cryptic "Sign in to confirm you're not a bot" line. if lower.contains("not a bot") || lower.contains("reloaded") { return "jutup uznaje nas za bota i blokuje pobieranie. Spróbuj ponownie ___**NIGDY**___. Spróbuj pobrać z jutup jeszcze raz, to cie powiesze za jaja.".into(); } // Generic extractor fallback means the URL didn't match any known // site — combined with "Requested format is not available" this is // almost always "the user pasted a non-video URL into /download". if lower.contains("requested format is not available") || lower.contains("url could be a direct video link") || lower.contains("falling back on generic information extractor") { return "Ten link nie wygląda na wideo obsługiwane przez yt-dlp. Upewnij się, że to adres do filmu (YouTube / SoundCloud / Bandcamp / Twitch).".into(); } if lower.contains("private video") { return "Ten film jest prywatny i nie można go pobrać.".into(); } if lower.contains("video unavailable") || lower.contains("video has been removed") || lower.contains("this video is unavailable") { return "Ten film jest niedostępny lub został usunięty.".into(); } if lower.contains("members-only") || lower.contains("premium-only") || lower.contains("channel's members") { return "Ten film jest dostępny tylko dla subskrybujących / członków kanału.".into(); } if lower.contains("not available in your country") || lower.contains("geo-restricted") || lower.contains("geo restricted") { return "Ten film jest zablokowany regionalnie i nie jest dostępny w naszym regionie." .into(); } if lower.contains("age-restricted") || lower.contains("age restricted") || lower.contains("inappropriate for some users") { return "Ten film jest ograniczony wiekowo i wymaga uwierzytelnienia.".into(); } if lower.contains("http error 404") || lower.contains(" http error 404 ") { return "Nie znaleziono takiego adresu (404).".into(); } if lower.contains("unable to extract") && lower.contains("video data") { return "Nie udało się wyciągnąć danych filmu — strona mogła się zmienić albo link jest niepełny.".into(); } // Fallback: keep only the first ERROR: line and strip the // `ERROR: [extractor] id: ` prefix so the message is short and // free of yt-dlp internals. for line in stderr.lines() { if let Some(rest) = line.strip_prefix("ERROR:") { let rest = rest.trim(); // Drop leading `[extractor] video_id: ` noise if present. let msg = rest .strip_prefix(|c: char| c == '[') .and_then(|r| r.find(']').map(|i| &r[i + 1..])) .map(|s| { s.trim_start_matches(|c: char| { c.is_alphanumeric() || c == '_' || c == '-' || c == ':' }) .trim() }) .filter(|s| !s.is_empty()) .unwrap_or(rest); return format!("Pobieranie się nie powiodło: `{msg}`"); } } for line in stderr.lines() { if let Some(rest) = line.strip_prefix("WARNING:") { return format!("Pobieranie się nie powiodło: `{}`", rest.trim()); } } "Pobieranie się nie powiodło.".into() } // ── Web command ─────────────────────────────────────────────────────── pub const L_WEB_NO_RESULTS: &str = "Brak rezultatów"; pub fn fmt_web_title(query: &str) -> String { format!("Wyniki wyszukiwania dla: `{query}`") } // ── QR command ──────────────────────────────────────────────────────── pub const L_QR_GENERATED: &str = "Kod QR wygenerowany:"; pub const L_QR_DECODE_FAILED: &str = "Nie udało się zdekodować kodu QR."; pub fn fmt_qr_decoded(text: &str) -> String { format!("```\n{text}\n```") } // ── Copyparty client ────────────────────────────────────────────────── pub const L_CP_UPLOAD_NO_URL: &str = "Wysłanie zawiodło, brak URL w odpowiedzi"; pub fn fmt_cp_search_http(status: &dyn std::fmt::Display, snippet: &str) -> String { format!("copyparty zwróciło status HTTP `{status}`: `{snippet}`") } pub fn fmt_cp_invalid_json(e: &dyn std::fmt::Display, snippet: &str) -> String { format!("copyparty zwróciło nielegalny JSON. (`{e}`): `{snippet}`") } // ── OpenRouter ──────────────────────────────────────────────────────── pub fn fmt_llm_err(endpoint: &str, status: &dyn std::fmt::Display, text: &str) -> String { format!("Błąd API LLM (`{endpoint}`). `{status}`: `{text}`") } // ── Voice / audio decoding ──────────────────────────────────────────── pub const L_VOICE_NO_TRACK: &str = "Brak kawałka"; pub const L_VOICE_NO_CODEC: &str = "Brak parametrów codec'a audio"; pub fn fmt_voice_remove_failed(e: &dyn std::fmt::Display) -> String { format!("Nie udało się usunąć klienta głosowego: `{e}`") } pub fn fmt_voice_probe_failed(e: &dyn std::fmt::Display) -> String { format!("Próbkowanie symphonia zawiodło: `{e}`") } pub fn fmt_voice_decoder_failed(e: &dyn std::fmt::Display) -> String { format!("Nie udało się utworzyć dekodera audio: `{e}`") } pub fn fmt_voice_ytdlp_launch(e: &dyn std::fmt::Display) -> String { format!("yt-dlp nie mogło się uruchomić: `{e}`") } pub fn fmt_voice_ffmpeg_launch(e: &dyn std::fmt::Display) -> String { format!("ffmpeg nie mogło się uruchomić: `{e}`") } // ── Events ──────────────────────────────────────────────────────────── pub const L_EV_READY: &str = "ŁUKASZ SPERMA AKTYWACJA"; pub const L_EV_VOICE_STATE_ERROR: &str = "Błąd stanu głosu"; pub const L_EV_MESSAGE_HANDLER_ERROR: &str = "Błąd ustanawiacza głosu"; // ── Event TTS phrases ───────────────────────────────────────────────── pub fn fmt_ev_joined(name: &str) -> String { format!("{name} sie zespawnił") } pub fn fmt_ev_left(name: &str) -> String { format!("{name} jest chujem") } pub fn fmt_ev_deafened(name: &str) -> String { format!("{name} jest bity przez ojca") } pub fn fmt_ev_undeafened(name: &str) -> String { format!("{name} uciekł od ojca") } pub fn fmt_ev_muted(name: &str) -> String { format!("{name} idzie robić jajecznice") } pub fn fmt_ev_unmuted(name: &str) -> String { format!("{name} zrobił jajecznice") } // —— Event log phrases ————————————————————————————————————————————————— pub fn fmt_log_ev_joined( current_time: &str, name: &str, current_ts: i64, channel_name: &str, ) -> String { format!( "[{current_time}] `{name}` dołączył na {} ", channel_name ) } pub fn fmt_log_ev_left( current_time: &str, name: &str, current_ts: i64, channel_name: &str, active_str: &str, afk_str: &str, ) -> String { format!( "[{current_time}] `{name}` rozłączył się z {} \n\ Aktywny: {active_str} | AFK: {afk_str}", channel_name, ) } pub fn fmt_log_ev_activity( current_time: &str, name: &str, current_ts: i64, channel_name: &str, afk_str: &str, ) -> String { // Always "odgłuchnął": this fn is only called when !is_afk, so the user // cannot still be self-muted. format!( "[{current_time}] `{name}` odgłuchnął się na {channel_name} \n\ AFK: {afk_str}" ) } pub const L_EV_WORD_MUTED: &str = "wyciszył"; pub const L_EV_WORD_DEAFENED: &str = "ogłuchnął"; pub fn fmt_log_ev_unactivity( current_time: &str, name: &str, current_ts: i64, channel_name: &str, activity: &str, active_str: &str, ) -> String { format!( "[{current_time}] `{name}` {activity} się na {channel_name} \n\ Aktywny: {active_str}" ) } pub fn fmt_log_ev_left_no_data( current_time: &str, name: &str, current_ts: i64, channel_name: &str, ) -> String { format!("[{current_time}] `{name}` rozłączył się z {channel_name} ") } // ── AI assistant (events/message handler) ───────────────────────────── pub const ASSISTANT_SYSTEM_PROMPT: &str = "Jesteś asystentem. Odpowiadasz po polsku, jesteś pomocny."; // ── Convenience helpers for repeated Error::Custom patterns ──────────── use crate::error::Error; pub fn err_not_in_guild() -> Error { Error::Custom(L_NOT_IN_GUILD.into()) } pub fn err_not_in_voice() -> Error { Error::Custom(L_NOT_IN_VOICE.into()) } pub fn err_trigger_not_found() -> Error { Error::Custom(L_TRIGGER_NOT_FOUND.into()) } pub fn err_bad_channel_type() -> Error { Error::Custom(L_BAD_CHANNEL_TYPE.into()) }