From b07a56082ac5e3e64de1fa72022532c7d8ba7ae2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jaroslav=20Bene=C5=A1?= Date: Sun, 9 Aug 2026 01:22:08 +0200 Subject: [PATCH] Fall back to English instead of showing raw keys Every namespace the fork added -- main_tabs, feed, cards, watch, browse, search, player, and now account -- exists only in en.json, because the thirteen translated language files predate them. get_text() consults the embedded fallback dict and then gives up and returns the key, so a non-English UI read "main_tabs.watch" on its own tabs. This round made that worse by adding more English-only keys. The fallback dict now carries those namespaces verbatim from en.json. It does not translate anything -- a German UI shows English for these strings -- but nothing shows a dotted key, and keys added later degrade the same way instead of breaking. Translations that do exist still win: buttons.download is still "Herunterladen" in German. Verified across all fourteen language files: no key in the fork's namespaces resolves to itself in any of them. Co-Authored-By: Claude Opus 5 (1M context) --- CHANGELOG.md | 7 ++ ytsage/utils/ytsage_localization.py | 136 +++++++++++++++++++++++++++- 2 files changed, 140 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0752634..cc135e6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -88,6 +88,13 @@ records. ### Fixed +- **A non-English interface no longer shows raw keys.** Every string SageTube + added — the tab names, the feed, cards, watch, browse, search, player and + account text — exists only in `en.json`, because the thirteen translated files + predate the fork. `get_text` returned the key itself, so the tabs literally + read `main_tabs.watch`. Those namespaces are now in the embedded English + fallback, so a missing translation reads as English rather than as a dotted + key. Translating the language files remains a separate job. - The error log filled with hundreds of `after creating texture: OpenGL error INVALID_ENUM` lines per session. They come from the GL driver, playback continues regardless, and they buried the errors that do matter, so they are diff --git a/ytsage/utils/ytsage_localization.py b/ytsage/utils/ytsage_localization.py index 9efba31..3eccd3e 100644 --- a/ytsage/utils/ytsage_localization.py +++ b/ytsage/utils/ytsage_localization.py @@ -78,9 +78,139 @@ class LocalizationManager: "check_app_updates": "Check for SageTube updates on startup", "check_beta_updates": "Receive Beta Updates" }, - # Only the keys added by SageTube live here; the rest of update_dialog - # is in every language file already. Without this a non-English UI - # would show the raw key for the buttons below. + # --- SageTube's own strings ------------------------------------- + # These namespaces exist only in en.json: the fork added them and the + # thirteen translated files predate them. get_text() falls back to this + # dict before giving up and returning the key itself, so without these + # a non-English UI literally reads "main_tabs.watch" on its tabs. + # Keep in step with en.json when adding keys; translating the language + # files is the separate, larger job. + "main_tabs": { + "watch": "Watch", + "search": "Search", + "feed": "Feed", + "browse": "Browse", + "downloads": "Downloads" + }, + "search": { + "placeholder": "Search YouTube...", + "button": "Search", + "searching": "Searching...", + "results_count": "{count} results", + "failed": "Search failed: {error}", + "button_tooltip": "Search YouTube" + }, + "browse": { + "placeholder": "Paste a channel or playlist URL...", + "open": "Open", + "subscribe": "Subscribe", + "unsubscribe": "Unsubscribe", + "play_all": "Play all", + "download_playlist": "Download", + "tab_videos": "Videos", + "tab_shorts": "Shorts", + "tab_live": "Live", + "loading": "Loading...", + "entry_count": "{count} videos", + "failed": "Failed to load: {error}", + "hint": "Open a channel (youtube.com/@name) or playlist URL, or navigate here from Search.", + "open_tooltip": "Open this channel or playlist", + "subscribe_tooltip": "Follow this channel so its uploads appear in your Feed", + "unsubscribe_tooltip": "Stop following this channel", + "play_all_tooltip": "Queue everything loaded here", + "download_playlist_tooltip": "Open this playlist in Downloads", + "queued_loaded": "Queued {count} loaded videos — press Load more first to queue the rest" + }, + "feed": { + "mode_local": "Local subscriptions", + "mode_account": "YouTube account (cookies)", + "refresh": "Refresh", + "subscriptions": "Subscriptions", + "no_subscriptions": "No subscriptions yet - subscribe from a channel page in Browse", + "refreshing": "Refreshing... {done}/{total}", + "refreshed": "{count} videos in feed", + "channel_failed": "A channel failed to refresh: {error}", + "fetching_account": "Fetching your subscription feed...", + "account_failed": "Account feed failed (are cookies valid?): {error}", + "subscribed": "Subscribed to {title}", + "unsubscribed": "Unsubscribed from {title}", + "open_channel": "Open channel", + "refresh_tooltip": "Fetch the latest uploads from every subscribed channel", + "mode_tooltip": "Where the feed comes from: your local subscriptions, or your signed-in YouTube account", + "refreshed_with_failures": "{count} videos — {failed} channel(s) could not be reached", + "account_needs_refresh": "Press Refresh to load your account feed", + "empty_no_subscriptions": "No subscriptions yet.\n\nFind channels in Search or Browse, then use Subscribe to follow them — their new uploads will appear here.", + "empty_not_refreshed": "Nothing here yet.\n\nPress Refresh to fetch the latest uploads from your subscriptions.", + "sign_in": "Sign in with cookies…", + "sign_in_tooltip": "Use your YouTube account via browser cookies, which enables the account feed", + "account_requires_cookies": "Requires signing in with cookies" + }, + "cards": { + "play": "▶ Play", + "queue": "+ Queue", + "download": "⬇", + "load_more": "Load more", + "empty": "Nothing here yet", + "play_tooltip": "Play this video now", + "queue_tooltip": "Add to the play queue", + "download_tooltip": "Open in Downloads with this video ready to analyse", + "load_more_tooltip": "Load the next page of results" + }, + "watch": { + "clear_queue": "Clear queue" + }, + "player": { + "quality_auto": "Auto", + "subtitles": "CC", + "unavailable": "Video player unavailable", + "now_playing": "Now Playing", + "queue": "Queue", + "play": "Play", + "pause": "Pause", + "stream_stalled": "Stream failed to start after several attempts - YouTube may be throttling. Try again or pick a lower quality.", + "play_pause": "Play / pause (Space)", + "fullscreen": "Fullscreen (F)", + "seek_tooltip": "Seek through the video", + "quality_tooltip": "Maximum playback resolution", + "speed_tooltip": "Playback speed", + "volume_tooltip": "Volume", + "subtitles_tooltip": "Toggle subtitles (C)", + "previous": "Previous in queue (P)", + "next": "Next in queue (N)", + "buffering": "Buffering…", + "act_play_pause": "Play / pause", + "act_seek_back_5": "Back 5 seconds", + "act_seek_fwd_5": "Forward 5 seconds", + "act_seek_back_10": "Back 10 seconds", + "act_seek_fwd_10": "Forward 10 seconds", + "act_seek_back_1": "Back 1 second", + "act_seek_fwd_1": "Forward 1 second", + "act_frame_back": "Previous frame", + "act_frame_fwd": "Next frame", + "act_vol_up": "Volume up", + "act_vol_down": "Volume down", + "act_mute": "Mute", + "act_unmute": "Unmute", + "act_speed_up": "Speed up", + "act_speed_down": "Slow down", + "act_speed_reset": "Normal speed", + "act_subtitles": "Subtitles on / off", + "act_fullscreen": "Fullscreen", + "act_leave_fullscreen": "Leave fullscreen", + "act_next": "Next in queue", + "act_previous": "Previous", + "act_start": "Back to start", + "act_end": "Jump to end", + "act_copy_url": "Copy video link", + "act_open_browser": "Open in browser" + }, + "account": { + "signed_out": "Not signed in", + "signed_out_tooltip": "SageTube is not using a YouTube account. Sign in with browser cookies to use your subscription feed and reach age-restricted or members-only videos.", + "signed_in": "Account: {source}", + "signed_in_tooltip": "Using cookies from {source}. Click to change or clear them.", + "source_file": "cookie file" + }, "update_dialog": { "skip_version": "Skip This Version", "skip_version_tooltip": "Never offer {version} again. Later versions will still be shown.",