From cd8ed8bc5793985df2c1692072a932ceee517b95 Mon Sep 17 00:00:00 2001 From: salmonik Date: Wed, 8 Jul 2026 02:13:35 +0200 Subject: OPEN THE RUST GATES Signed-off-by: salmonik --- src/lang.rs | 387 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 387 insertions(+) create mode 100644 src/lang.rs (limited to 'src/lang.rs') diff --git a/src/lang.rs b/src/lang.rs new file mode 100644 index 0000000..626f387 --- /dev/null +++ b/src/lang.rs @@ -0,0 +1,387 @@ +// ┌──────────────────────────────────────────────────────────────────┐ +// │ 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."; + +pub const L_OPENROUTER_NO_KEY: &str = "Klucz API OpenRouter nie jest skonfigurowany w .env"; + +// ── 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("sign in to confirm you") + || lower.contains("confirm you're not a bot") + || lower.contains("confirm you are not a bot") + { + return "YouTube uznaje nas za bota i blokuje pobieranie. Spróbuj ponownie później albo podaj inny link.".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_openrouter_err(status: &dyn std::fmt::Display, text: &str) -> String { + format!("Błąd API OpenRouter. {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") +} + +// ── AI assistant (events/message handler) ───────────────────────────── + +pub const ASSISTANT_SYSTEM_PROMPT: &str = + "Jesteś asystentem. Odpowiadasz po polsku, jesteś pomocny, nie używasz emoji."; + +// ── 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()) +} -- cgit v1.2.3