feat(channels): dedicated channel page + ephemeral explore UI

Frontend for the channel-explore feature:
- ChannelPage: banner/avatar/stats header, Subscribe/unsubscribe, an "exploring" badge
  while browsing an un-subscribed channel, Videos/About tabs. Reuses Feed scoped to the
  channel (scope=all + source=all so the per-user ephemeral videos show). Auto-ingests
  recent uploads on first visit (background, with a loading note) + "Load more from
  YouTube" to page deeper; skipped for demo / already-subscribed channels.
- App: openChannel/closeChannel as a Back-aware sub-view (history.state._chan, mirrors the
  YT-search _yt pattern); ChannelPage takes over the content column, nav rail stays.
- ChannelLink/cards/player: the channel name now opens our channel page (onChannelFilter →
  onOpenChannel); the in-card "only this channel" filter button is dropped (the page
  subsumes it). PlayerModal channel-name wiring follows in the next commit.
- api: channelDetail + exploreChannel; ChannelDetail/ExploreResult types.
- i18n EN/HU/DE: channel namespace, explore_cleanup scheduler labels, explore config group,
  channels_explore quota label.
This commit is contained in:
npeter83 2026-06-30 03:08:52 +02:00
parent bc4c362423
commit cc1e670202
18 changed files with 744 additions and 120 deletions

View file

@ -0,0 +1,20 @@
{
"back": "Zurück",
"exploringBadge": "Erkunden",
"subscribe": "Abonnieren",
"subscribedState": "Abonniert",
"unsubscribe": "Abbestellen",
"unsubTitle": "Abbestellen?",
"unsubBody": "{{name}} auf YouTube nicht mehr folgen?",
"subscribed": "{{name}} abonniert",
"subscribers": "{{formatted}} Abonnenten",
"videoCount_one": "{{count}} Video",
"videoCount_other": "{{count}} Videos",
"totalViews": "{{formatted}} Aufrufe",
"joined": "Beigetreten {{date}}",
"loadingVideos": "Videos dieses Kanals werden geladen…",
"tabVideos": "Videos",
"tabAbout": "Info",
"loadMore": "Mehr von YouTube laden",
"noDescription": "Dieser Kanal hat keine Beschreibung."
}

View file

@ -9,29 +9,94 @@
"quota": "Kontingent",
"backfill": "Nachladen (Backfill)",
"shorts": "Shorts-Prüfung",
"batch": "Stapelgrößen"
"batch": "Stapelgrößen",
"explore": "Kanal erkunden"
},
"fields": {
"smtp_host": { "label": "SMTP-Host", "hint": "z. B. smtp.gmail.com" },
"smtp_port": { "label": "SMTP-Port", "hint": "Üblicherweise 587 (STARTTLS)." },
"smtp_user": { "label": "SMTP-Benutzername", "hint": "Das sendende Konto / der Login." },
"smtp_from": { "label": "Absenderadresse", "hint": "z. B. Siftlode <addr@gmail.com>. Standard ist der Benutzername." },
"smtp_password": { "label": "SMTP-Passwort", "hint": "App-Passwort. Verschlüsselt gespeichert; nur schreibbar — wird nie wieder angezeigt." },
"quota_daily_budget": { "label": "Tägliches Kontingentbudget", "hint": "YouTube-Data-API-Einheiten pro Tag (kostenloses Limit 10.000). Standard 9000 lässt Puffer." },
"backfill_quota_reserve": { "label": "Nachlade-Kontingentreserve", "hint": "Reservierte Einheiten, damit geplantes Nachladen interaktive Syncs / Anreicherung nie aushungert." },
"search_daily_limit_per_user": { "label": "Live-Suche — Tageslimit pro Nutzer", "hint": "Maximale Live-YouTube-Suchen pro Nutzer und Tag. Bei der API-Quelle kostet jede Suche 100 Einheiten (das gemeinsame Budget reicht also für insgesamt nur ~80-90/Tag); bei der Scrape-Quelle ist es ein reines Missbrauchslimit. 0 = Live-Suche deaktiviert." },
"search_source": { "label": "Quelle der Live-Suche", "hint": "Woher die Ergebnisse der Live-YouTube-Suche stammen. \"scrape\" nutzt YouTubes internen Endpunkt und verbraucht kein API-Kontingent (empfohlen). \"api\" nutzt das offizielle search.list (100 Einheiten/Seite). Auf \"api\" umstellen, falls das Scraping ausfällt oder blockiert wird." },
"backfill_recent_max_videos": { "label": "Aktuelles Nachladen — max. Videos", "hint": "Neueste Videos pro Kanal beim ersten Durchlauf." },
"backfill_recent_max_days": { "label": "Aktuelles Nachladen — max. Alter (Tage)", "hint": "Wie weit der erste Durchlauf pro Kanal zurückreicht." },
"shorts_probe_max_seconds": { "label": "Shorts-Prüfung — max. Dauer (s)", "hint": "Nur Videos bis zu dieser Länge werden als mögliche Shorts geprüft." },
"shorts_probe_batch": { "label": "Shorts-Prüfung — Stapelgröße", "hint": "Wie viele Videos pro Lauf klassifiziert werden." },
"enrich_batch_size": { "label": "Anreicherungs-Stapelgröße", "hint": "videos.list-IDs pro Aufruf (YouTube begrenzt auf 50)." },
"autotag_title_sample": { "label": "Auto-Tag-Titelstichprobe", "hint": "Pro Kanal abgetastete aktuelle Videotitel zur Spracherkennung." },
"youtube_api_key": { "label": "YouTube-API-Schlüssel", "hint": "Optional. Für öffentliche Lesezugriffe (Kanäle/Videos), damit gemeinsames Nachladen nicht vom OAuth eines Nutzers abhängt. Verschlüsselt gespeichert; nur schreibbar." },
"youtube_api_proxy": { "label": "YouTube-API-Egress-Proxy", "hint": "Optional. HTTP(S)-Proxy-URL für ausgehende YouTube-API-Aufrufe — über einen Host mit fester IP leiten, damit ein IP-beschränkter Schlüssel von einem Server mit dynamischer IP funktioniert. Leer = direkt." },
"google_client_id": { "label": "Google-Client-ID", "hint": "OAuth-2.0-Client-ID für die Google-Anmeldung. Verschlüsselt gespeichert; nur schreibbar. Beide Google-Felder leer lassen, um die Google-Anmeldung zu deaktivieren (E-Mail+Passwort funktioniert weiter)." },
"google_client_secret": { "label": "Google-Client-Secret", "hint": "OAuth-2.0-Client-Secret. Verschlüsselt gespeichert; nur schreibbar." },
"allow_registration": { "label": "Registrierung erlauben", "hint": "Wenn aktiviert, kann jeder eine E-Mail+Passwort-Registrierung einreichen. Für die Anmeldung sind weiterhin E-Mail-Bestätigung und Admin-Freigabe nötig." }
"smtp_host": {
"label": "SMTP-Host",
"hint": "z. B. smtp.gmail.com"
},
"smtp_port": {
"label": "SMTP-Port",
"hint": "Üblicherweise 587 (STARTTLS)."
},
"smtp_user": {
"label": "SMTP-Benutzername",
"hint": "Das sendende Konto / der Login."
},
"smtp_from": {
"label": "Absenderadresse",
"hint": "z. B. Siftlode <addr@gmail.com>. Standard ist der Benutzername."
},
"smtp_password": {
"label": "SMTP-Passwort",
"hint": "App-Passwort. Verschlüsselt gespeichert; nur schreibbar — wird nie wieder angezeigt."
},
"quota_daily_budget": {
"label": "Tägliches Kontingentbudget",
"hint": "YouTube-Data-API-Einheiten pro Tag (kostenloses Limit 10.000). Standard 9000 lässt Puffer."
},
"backfill_quota_reserve": {
"label": "Nachlade-Kontingentreserve",
"hint": "Reservierte Einheiten, damit geplantes Nachladen interaktive Syncs / Anreicherung nie aushungert."
},
"search_daily_limit_per_user": {
"label": "Live-Suche — Tageslimit pro Nutzer",
"hint": "Maximale Live-YouTube-Suchen pro Nutzer und Tag. Bei der API-Quelle kostet jede Suche 100 Einheiten (das gemeinsame Budget reicht also für insgesamt nur ~80-90/Tag); bei der Scrape-Quelle ist es ein reines Missbrauchslimit. 0 = Live-Suche deaktiviert."
},
"search_source": {
"label": "Quelle der Live-Suche",
"hint": "Woher die Ergebnisse der Live-YouTube-Suche stammen. \"scrape\" nutzt YouTubes internen Endpunkt und verbraucht kein API-Kontingent (empfohlen). \"api\" nutzt das offizielle search.list (100 Einheiten/Seite). Auf \"api\" umstellen, falls das Scraping ausfällt oder blockiert wird."
},
"backfill_recent_max_videos": {
"label": "Aktuelles Nachladen — max. Videos",
"hint": "Neueste Videos pro Kanal beim ersten Durchlauf."
},
"backfill_recent_max_days": {
"label": "Aktuelles Nachladen — max. Alter (Tage)",
"hint": "Wie weit der erste Durchlauf pro Kanal zurückreicht."
},
"shorts_probe_max_seconds": {
"label": "Shorts-Prüfung — max. Dauer (s)",
"hint": "Nur Videos bis zu dieser Länge werden als mögliche Shorts geprüft."
},
"shorts_probe_batch": {
"label": "Shorts-Prüfung — Stapelgröße",
"hint": "Wie viele Videos pro Lauf klassifiziert werden."
},
"enrich_batch_size": {
"label": "Anreicherungs-Stapelgröße",
"hint": "videos.list-IDs pro Aufruf (YouTube begrenzt auf 50)."
},
"autotag_title_sample": {
"label": "Auto-Tag-Titelstichprobe",
"hint": "Pro Kanal abgetastete aktuelle Videotitel zur Spracherkennung."
},
"youtube_api_key": {
"label": "YouTube-API-Schlüssel",
"hint": "Optional. Für öffentliche Lesezugriffe (Kanäle/Videos), damit gemeinsames Nachladen nicht vom OAuth eines Nutzers abhängt. Verschlüsselt gespeichert; nur schreibbar."
},
"youtube_api_proxy": {
"label": "YouTube-API-Egress-Proxy",
"hint": "Optional. HTTP(S)-Proxy-URL für ausgehende YouTube-API-Aufrufe — über einen Host mit fester IP leiten, damit ein IP-beschränkter Schlüssel von einem Server mit dynamischer IP funktioniert. Leer = direkt."
},
"google_client_id": {
"label": "Google-Client-ID",
"hint": "OAuth-2.0-Client-ID für die Google-Anmeldung. Verschlüsselt gespeichert; nur schreibbar. Beide Google-Felder leer lassen, um die Google-Anmeldung zu deaktivieren (E-Mail+Passwort funktioniert weiter)."
},
"google_client_secret": {
"label": "Google-Client-Secret",
"hint": "OAuth-2.0-Client-Secret. Verschlüsselt gespeichert; nur schreibbar."
},
"allow_registration": {
"label": "Registrierung erlauben",
"hint": "Wenn aktiviert, kann jeder eine E-Mail+Passwort-Registrierung einreichen. Für die Anmeldung sind weiterhin E-Mail-Bestätigung und Admin-Freigabe nötig."
},
"explore_grace_days": {
"label": "Erkunden — Karenzzeit (Tage)",
"hint": "Wie lange ein erkundeter, nicht abonnierter Kanal (und seine flüchtigen Videos) behalten wird, bevor die Aufräumaufgabe ihn entfernt. Ein Abo behält ihn dauerhaft."
}
},
"save": "Speichern",
"saving": "Speichern…",

View file

@ -13,5 +13,6 @@
"channels_subscribe": "Abonnieren",
"channels_unsubscribe": "Abo beenden",
"maintenance_revalidate": "Wartungs-Neuvalidierung",
"other": "Sonstiges"
"other": "Sonstiges",
"channels_explore": "Kanal erkunden"
}

View file

@ -66,7 +66,8 @@
"shorts": "Prüft youtube.com/shorts, um Shorts zu markieren. Fällt er aus, werden Shorts im Feed nicht von normalen Videos getrennt.",
"subscriptions": "Importiert die YouTube-Abos jedes Nutzers neu. Fällt er aus, werden auf YouTube hinzugefügte oder entfernte Abos hier nicht übernommen.",
"playlist_sync": "Spiegelt die YouTube-Wiedergabelisten jedes Nutzers in die App. Fällt er aus, erscheinen Änderungen an deinen YouTube-Listen lokal nicht.",
"maintenance": "Findet Videos, die nicht mehr abspielbar sind (gelöscht, auf privat gesetzt, abgebrochene Premieren), blendet sie aus dem Feed aus und entfernt sie nach einer Schonfrist; prüft die am längsten nicht geprüften Videos erneut. Fällt er aus, bleiben tote Videos im Katalog."
"maintenance": "Findet Videos, die nicht mehr abspielbar sind (gelöscht, auf privat gesetzt, abgebrochene Premieren), blendet sie aus dem Feed aus und entfernt sie nach einer Schonfrist; prüft die am längsten nicht geprüften Videos erneut. Fällt er aus, bleiben tote Videos im Katalog.",
"explore_cleanup": "Entfernt Kanäle, die du erkundet, aber nie abonniert hast (und ihre Videos), nach einer Karenzzeit, damit Neugier-Stöbern den Katalog nicht überfüllt. Wenn es stoppt, bleiben verlassene erkundete Kanäle bestehen."
},
"jobs": {
"rss_poll": "RSS-Abfrage (neue Uploads)",
@ -77,7 +78,8 @@
"subscriptions": "Abo-Resync",
"playlist_sync": "YouTube-Wiedergabelisten-Sync",
"maintenance": "Wartung + Validierung",
"demo_reset": "Demo-Konto-Reset"
"demo_reset": "Demo-Konto-Reset",
"explore_cleanup": "Bereinigung erkundeter Kanäle"
},
"queue": {
"recentPending": "Zu synchronisierende Kanäle",

View file

@ -0,0 +1,20 @@
{
"back": "Back",
"exploringBadge": "Exploring",
"subscribe": "Subscribe",
"subscribedState": "Subscribed",
"unsubscribe": "Unsubscribe",
"unsubTitle": "Unsubscribe?",
"unsubBody": "Stop following {{name}} on YouTube?",
"subscribed": "Subscribed to {{name}}",
"subscribers": "{{formatted}} subscribers",
"videoCount_one": "{{count}} video",
"videoCount_other": "{{count}} videos",
"totalViews": "{{formatted}} views",
"joined": "Joined {{date}}",
"loadingVideos": "Loading this channel's videos…",
"tabVideos": "Videos",
"tabAbout": "About",
"loadMore": "Load more from YouTube",
"noDescription": "This channel has no description."
}

View file

@ -9,29 +9,94 @@
"quota": "Quota",
"backfill": "Backfill",
"shorts": "Shorts probe",
"batch": "Batch sizes"
"batch": "Batch sizes",
"explore": "Channel explore"
},
"fields": {
"smtp_host": { "label": "SMTP host", "hint": "e.g. smtp.gmail.com" },
"smtp_port": { "label": "SMTP port", "hint": "Usually 587 (STARTTLS)." },
"smtp_user": { "label": "SMTP username", "hint": "The sending account / login." },
"smtp_from": { "label": "From address", "hint": "e.g. Siftlode <addr@gmail.com>. Defaults to the username." },
"smtp_password": { "label": "SMTP password", "hint": "App password. Stored encrypted; write-only — it's never shown back." },
"quota_daily_budget": { "label": "Daily quota budget", "hint": "Total YouTube Data API units to spend per day (free tier is 10,000). Default 9000 leaves headroom." },
"backfill_quota_reserve": { "label": "Backfill quota reserve", "hint": "Units kept in reserve so scheduled backfill never starves interactive syncs / enrichment." },
"search_daily_limit_per_user": { "label": "Live search — daily limit per user", "hint": "Max live YouTube searches per user per day. With the API source each search costs 100 units (so the shared budget affords only ~80-90/day total); with the scrape source it's a pure anti-abuse limit. Set 0 to disable live search." },
"search_source": { "label": "Live search source", "hint": "Where live YouTube search results come from. \"scrape\" uses YouTube's internal endpoint and spends no API quota (recommended). \"api\" uses the official search.list (100 units/page). Switch to \"api\" if scraping ever breaks or is blocked." },
"backfill_recent_max_videos": { "label": "Recent backfill — max videos", "hint": "Newest videos fetched per channel on the first pass." },
"backfill_recent_max_days": { "label": "Recent backfill — max age (days)", "hint": "How far back the first pass reaches per channel." },
"shorts_probe_max_seconds": { "label": "Shorts probe — max duration (s)", "hint": "Only videos at or below this length are probed as possible Shorts." },
"shorts_probe_batch": { "label": "Shorts probe — batch size", "hint": "How many videos to classify per run." },
"enrich_batch_size": { "label": "Enrichment batch size", "hint": "videos.list ids per call (YouTube caps this at 50)." },
"autotag_title_sample": { "label": "Auto-tag title sample", "hint": "Recent video titles sampled per channel for language detection." },
"youtube_api_key": { "label": "YouTube API key", "hint": "Optional. Used for public reads (channels/videos) so shared backfill doesn't depend on a user's OAuth. Stored encrypted; write-only." },
"youtube_api_proxy": { "label": "YouTube API egress proxy", "hint": "Optional. HTTP(S) proxy URL for outbound YouTube API calls — route them through a fixed-IP host so an IP-restricted key keeps working from a dynamic-IP server. Leave empty for direct." },
"google_client_id": { "label": "Google client ID", "hint": "OAuth 2.0 client ID for Sign in with Google. Stored encrypted; write-only. Leave both Google fields empty to disable Google sign-in (email+password still works)." },
"google_client_secret": { "label": "Google client secret", "hint": "OAuth 2.0 client secret. Stored encrypted; write-only." },
"allow_registration": { "label": "Allow registration", "hint": "When on, anyone can submit an email+password registration. They still need to verify their email and be approved by an admin before they can sign in." }
"smtp_host": {
"label": "SMTP host",
"hint": "e.g. smtp.gmail.com"
},
"smtp_port": {
"label": "SMTP port",
"hint": "Usually 587 (STARTTLS)."
},
"smtp_user": {
"label": "SMTP username",
"hint": "The sending account / login."
},
"smtp_from": {
"label": "From address",
"hint": "e.g. Siftlode <addr@gmail.com>. Defaults to the username."
},
"smtp_password": {
"label": "SMTP password",
"hint": "App password. Stored encrypted; write-only — it's never shown back."
},
"quota_daily_budget": {
"label": "Daily quota budget",
"hint": "Total YouTube Data API units to spend per day (free tier is 10,000). Default 9000 leaves headroom."
},
"backfill_quota_reserve": {
"label": "Backfill quota reserve",
"hint": "Units kept in reserve so scheduled backfill never starves interactive syncs / enrichment."
},
"search_daily_limit_per_user": {
"label": "Live search — daily limit per user",
"hint": "Max live YouTube searches per user per day. With the API source each search costs 100 units (so the shared budget affords only ~80-90/day total); with the scrape source it's a pure anti-abuse limit. Set 0 to disable live search."
},
"search_source": {
"label": "Live search source",
"hint": "Where live YouTube search results come from. \"scrape\" uses YouTube's internal endpoint and spends no API quota (recommended). \"api\" uses the official search.list (100 units/page). Switch to \"api\" if scraping ever breaks or is blocked."
},
"backfill_recent_max_videos": {
"label": "Recent backfill — max videos",
"hint": "Newest videos fetched per channel on the first pass."
},
"backfill_recent_max_days": {
"label": "Recent backfill — max age (days)",
"hint": "How far back the first pass reaches per channel."
},
"shorts_probe_max_seconds": {
"label": "Shorts probe — max duration (s)",
"hint": "Only videos at or below this length are probed as possible Shorts."
},
"shorts_probe_batch": {
"label": "Shorts probe — batch size",
"hint": "How many videos to classify per run."
},
"enrich_batch_size": {
"label": "Enrichment batch size",
"hint": "videos.list ids per call (YouTube caps this at 50)."
},
"autotag_title_sample": {
"label": "Auto-tag title sample",
"hint": "Recent video titles sampled per channel for language detection."
},
"youtube_api_key": {
"label": "YouTube API key",
"hint": "Optional. Used for public reads (channels/videos) so shared backfill doesn't depend on a user's OAuth. Stored encrypted; write-only."
},
"youtube_api_proxy": {
"label": "YouTube API egress proxy",
"hint": "Optional. HTTP(S) proxy URL for outbound YouTube API calls — route them through a fixed-IP host so an IP-restricted key keeps working from a dynamic-IP server. Leave empty for direct."
},
"google_client_id": {
"label": "Google client ID",
"hint": "OAuth 2.0 client ID for Sign in with Google. Stored encrypted; write-only. Leave both Google fields empty to disable Google sign-in (email+password still works)."
},
"google_client_secret": {
"label": "Google client secret",
"hint": "OAuth 2.0 client secret. Stored encrypted; write-only."
},
"allow_registration": {
"label": "Allow registration",
"hint": "When on, anyone can submit an email+password registration. They still need to verify their email and be approved by an admin before they can sign in."
},
"explore_grace_days": {
"label": "Explore — grace period (days)",
"hint": "How long an explored-but-unsubscribed channel (and its ephemeral videos) is kept before the cleanup job removes it. Subscribing keeps it permanently."
}
},
"save": "Save",
"saving": "Saving…",

View file

@ -13,5 +13,6 @@
"channels_subscribe": "Subscribe",
"channels_unsubscribe": "Unsubscribe",
"maintenance_revalidate": "Maintenance re-validation",
"other": "Other"
"other": "Other",
"channels_explore": "Channel explore"
}

View file

@ -66,7 +66,8 @@
"shorts": "Probes youtube.com/shorts to mark which videos are Shorts. If it stops, Shorts aren't separated from normal videos in the feed.",
"subscriptions": "Re-imports every user's YouTube subscriptions. If it stops, channels subscribed to or dropped on YouTube aren't reflected here.",
"playlist_sync": "Mirrors each user's YouTube playlists into the app. If it stops, changes to your YouTube playlists don't show up locally.",
"maintenance": "Finds videos that can no longer be played (deleted, made private, abandoned premieres), hides them from the feed and removes them after a grace period; re-checks the least-recently-checked videos. If it stops, dead videos linger in the catalogue."
"maintenance": "Finds videos that can no longer be played (deleted, made private, abandoned premieres), hides them from the feed and removes them after a grace period; re-checks the least-recently-checked videos. If it stops, dead videos linger in the catalogue.",
"explore_cleanup": "Removes channels you explored but never subscribed to (and their videos) after a grace period, so curiosity browsing doesn't pile up in the catalogue. If it stops, abandoned explored channels linger."
},
"jobs": {
"rss_poll": "RSS poll (new uploads)",
@ -77,7 +78,8 @@
"subscriptions": "Subscription resync",
"playlist_sync": "YouTube playlist sync",
"maintenance": "Maintenance + validation",
"demo_reset": "Demo account reset"
"demo_reset": "Demo account reset",
"explore_cleanup": "Explored-channel cleanup"
},
"queue": {
"recentPending": "Channels to sync",

View file

@ -0,0 +1,20 @@
{
"back": "Vissza",
"exploringBadge": "Felfedezés",
"subscribe": "Feliratkozás",
"subscribedState": "Feliratkozva",
"unsubscribe": "Leiratkozás",
"unsubTitle": "Leiratkozol?",
"unsubBody": "Megszünteted a(z) {{name}} követését a YouTube-on?",
"subscribed": "Feliratkoztál: {{name}}",
"subscribers": "{{formatted}} feliratkozó",
"videoCount_one": "{{count}} videó",
"videoCount_other": "{{count}} videó",
"totalViews": "{{formatted}} megtekintés",
"joined": "Csatlakozott: {{date}}",
"loadingVideos": "A csatorna videóinak betöltése…",
"tabVideos": "Videók",
"tabAbout": "Névjegy",
"loadMore": "Több betöltése a YouTube-ról",
"noDescription": "Ennek a csatornának nincs leírása."
}

View file

@ -9,29 +9,94 @@
"quota": "Kvóta",
"backfill": "Letöltés (backfill)",
"shorts": "Shorts-vizsgálat",
"batch": "Kötegméretek"
"batch": "Kötegméretek",
"explore": "Csatorna-felfedezés"
},
"fields": {
"smtp_host": { "label": "SMTP-kiszolgáló", "hint": "pl. smtp.gmail.com" },
"smtp_port": { "label": "SMTP-port", "hint": "Általában 587 (STARTTLS)." },
"smtp_user": { "label": "SMTP-felhasználónév", "hint": "A küldő fiók / bejelentkezés." },
"smtp_from": { "label": "Feladó cím", "hint": "pl. Siftlode <addr@gmail.com>. Alapból a felhasználónév." },
"smtp_password": { "label": "SMTP-jelszó", "hint": "Alkalmazásjelszó. Titkosítva tárolva; csak írható — soha nem jelenik meg újra." },
"quota_daily_budget": { "label": "Napi kvótakeret", "hint": "Naponta elkölthető YouTube Data API-egységek (az ingyenes keret 10 000). Az alap 9000 tartalékot hagy." },
"backfill_quota_reserve": { "label": "Letöltési kvótatartalék", "hint": "Tartalékban tartott egységek, hogy az ütemezett letöltés ne éheztesse ki az interaktív szinkront / gazdagítást." },
"search_daily_limit_per_user": { "label": "Élő keresés — napi limit / felhasználó", "hint": "Maximális élő YouTube-keresés felhasználónként naponta. Az API-forrásnál egy keresés 100 egységbe kerül (így a közös büdzséből összesen csak ~80-90 fér bele naponta); a scrape-forrásnál ez tiszta visszaélés-elleni limit. 0 = élő keresés kikapcsolva." },
"search_source": { "label": "Élő keresés forrása", "hint": "Honnan jönnek az élő YouTube-keresés találatai. A „scrape” a YouTube belső végpontját használja és nem fogyaszt API-kvótát (ajánlott). Az „api” a hivatalos search.list-et (100 egység/oldal). Válts „api”-ra, ha a scrape valaha elromlik vagy letiltják." },
"backfill_recent_max_videos": { "label": "Friss letöltés — max videó", "hint": "Csatornánként az első körben letöltött legújabb videók száma." },
"backfill_recent_max_days": { "label": "Friss letöltés — max kor (nap)", "hint": "Az első kör csatornánként meddig nyúl vissza." },
"shorts_probe_max_seconds": { "label": "Shorts-vizsgálat — max hossz (mp)", "hint": "Csak az ennél nem hosszabb videókat vizsgáljuk lehetséges Shortsként." },
"shorts_probe_batch": { "label": "Shorts-vizsgálat — kötegméret", "hint": "Futásonként ennyi videót osztályozunk." },
"enrich_batch_size": { "label": "Gazdagítási kötegméret", "hint": "videos.list azonosítók hívásonként (a YouTube 50-ben maximálja)." },
"autotag_title_sample": { "label": "Auto-címke címmintavétel", "hint": "Csatornánként ennyi friss videócímet mintázunk a nyelvfelismeréshez." },
"youtube_api_key": { "label": "YouTube API-kulcs", "hint": "Opcionális. Publikus olvasásokhoz (csatornák/videók), hogy a megosztott letöltés ne függjön egy felhasználó OAuth-jától. Titkosítva tárolva; csak írható." },
"youtube_api_proxy": { "label": "YouTube API egress proxy", "hint": "Opcionális. HTTP(S) proxy URL a kimenő YouTube API-hívásokhoz — fix IP-jű hoston átengedve egy IP-korlátozott kulcs dinamikus IP-jű szerverről is működik. Üresen = közvetlen." },
"google_client_id": { "label": "Google kliens-azonosító", "hint": "OAuth 2.0 kliens-azonosító a Google-bejelentkezéshez. Titkosítva tárolva; csak írható. Hagyd üresen mindkét Google-mezőt a Google-bejelentkezés kikapcsolásához (az e-mail+jelszó továbbra is működik)." },
"google_client_secret": { "label": "Google kliens-titok", "hint": "OAuth 2.0 kliens-titok. Titkosítva tárolva; csak írható." },
"allow_registration": { "label": "Regisztráció engedélyezése", "hint": "Bekapcsolva bárki beküldhet email+jelszó regisztrációt. Belépéshez továbbra is email-igazolás és admin-jóváhagyás szükséges." }
"smtp_host": {
"label": "SMTP-kiszolgáló",
"hint": "pl. smtp.gmail.com"
},
"smtp_port": {
"label": "SMTP-port",
"hint": "Általában 587 (STARTTLS)."
},
"smtp_user": {
"label": "SMTP-felhasználónév",
"hint": "A küldő fiók / bejelentkezés."
},
"smtp_from": {
"label": "Feladó cím",
"hint": "pl. Siftlode <addr@gmail.com>. Alapból a felhasználónév."
},
"smtp_password": {
"label": "SMTP-jelszó",
"hint": "Alkalmazásjelszó. Titkosítva tárolva; csak írható — soha nem jelenik meg újra."
},
"quota_daily_budget": {
"label": "Napi kvótakeret",
"hint": "Naponta elkölthető YouTube Data API-egységek (az ingyenes keret 10 000). Az alap 9000 tartalékot hagy."
},
"backfill_quota_reserve": {
"label": "Letöltési kvótatartalék",
"hint": "Tartalékban tartott egységek, hogy az ütemezett letöltés ne éheztesse ki az interaktív szinkront / gazdagítást."
},
"search_daily_limit_per_user": {
"label": "Élő keresés — napi limit / felhasználó",
"hint": "Maximális élő YouTube-keresés felhasználónként naponta. Az API-forrásnál egy keresés 100 egységbe kerül (így a közös büdzséből összesen csak ~80-90 fér bele naponta); a scrape-forrásnál ez tiszta visszaélés-elleni limit. 0 = élő keresés kikapcsolva."
},
"search_source": {
"label": "Élő keresés forrása",
"hint": "Honnan jönnek az élő YouTube-keresés találatai. A „scrape” a YouTube belső végpontját használja és nem fogyaszt API-kvótát (ajánlott). Az „api” a hivatalos search.list-et (100 egység/oldal). Válts „api”-ra, ha a scrape valaha elromlik vagy letiltják."
},
"backfill_recent_max_videos": {
"label": "Friss letöltés — max videó",
"hint": "Csatornánként az első körben letöltött legújabb videók száma."
},
"backfill_recent_max_days": {
"label": "Friss letöltés — max kor (nap)",
"hint": "Az első kör csatornánként meddig nyúl vissza."
},
"shorts_probe_max_seconds": {
"label": "Shorts-vizsgálat — max hossz (mp)",
"hint": "Csak az ennél nem hosszabb videókat vizsgáljuk lehetséges Shortsként."
},
"shorts_probe_batch": {
"label": "Shorts-vizsgálat — kötegméret",
"hint": "Futásonként ennyi videót osztályozunk."
},
"enrich_batch_size": {
"label": "Gazdagítási kötegméret",
"hint": "videos.list azonosítók hívásonként (a YouTube 50-ben maximálja)."
},
"autotag_title_sample": {
"label": "Auto-címke címmintavétel",
"hint": "Csatornánként ennyi friss videócímet mintázunk a nyelvfelismeréshez."
},
"youtube_api_key": {
"label": "YouTube API-kulcs",
"hint": "Opcionális. Publikus olvasásokhoz (csatornák/videók), hogy a megosztott letöltés ne függjön egy felhasználó OAuth-jától. Titkosítva tárolva; csak írható."
},
"youtube_api_proxy": {
"label": "YouTube API egress proxy",
"hint": "Opcionális. HTTP(S) proxy URL a kimenő YouTube API-hívásokhoz — fix IP-jű hoston átengedve egy IP-korlátozott kulcs dinamikus IP-jű szerverről is működik. Üresen = közvetlen."
},
"google_client_id": {
"label": "Google kliens-azonosító",
"hint": "OAuth 2.0 kliens-azonosító a Google-bejelentkezéshez. Titkosítva tárolva; csak írható. Hagyd üresen mindkét Google-mezőt a Google-bejelentkezés kikapcsolásához (az e-mail+jelszó továbbra is működik)."
},
"google_client_secret": {
"label": "Google kliens-titok",
"hint": "OAuth 2.0 kliens-titok. Titkosítva tárolva; csak írható."
},
"allow_registration": {
"label": "Regisztráció engedélyezése",
"hint": "Bekapcsolva bárki beküldhet email+jelszó regisztrációt. Belépéshez továbbra is email-igazolás és admin-jóváhagyás szükséges."
},
"explore_grace_days": {
"label": "Felfedezés — türelmi idő (nap)",
"hint": "Meddig marad meg egy felfedezett, de nem feliratkozott csatorna (és efemer videói), mielőtt a takarító feladat törli. A feliratkozás véglegesen megtartja."
}
},
"save": "Mentés",
"saving": "Mentés…",

View file

@ -13,5 +13,6 @@
"channels_subscribe": "Feliratkozás",
"channels_unsubscribe": "Leiratkozás",
"maintenance_revalidate": "Karbantartó újraellenőrzés",
"other": "Egyéb"
"other": "Egyéb",
"channels_explore": "Csatorna-felfedezés"
}

View file

@ -66,7 +66,8 @@
"shorts": "A youtube.com/shorts próbával jelöli, mely videók Shorts-ok. Ha leáll, a Shorts-ok nem különülnek el a normál videóktól a hírfolyamban.",
"subscriptions": "Újraimportálja minden felhasználó YouTube-feliratkozásait. Ha leáll, a YouTube-on felvett vagy törölt feliratkozások nem tükröződnek itt.",
"playlist_sync": "Tükrözi a felhasználók YouTube lejátszási listáit az appba. Ha leáll, a YouTube-listák változásai nem jelennek meg lokálisan.",
"maintenance": "Megkeresi a már sehol le nem játszható videókat (törölt, priváttá tett, elhagyott premier), elrejti őket a hírfolyamból, és türelmi idő után eltávolítja; újraellenőrzi a legrégebben ellenőrzött videókat. Ha leáll, a halott videók a katalógusban maradnak."
"maintenance": "Megkeresi a már sehol le nem játszható videókat (törölt, priváttá tett, elhagyott premier), elrejti őket a hírfolyamból, és türelmi idő után eltávolítja; újraellenőrzi a legrégebben ellenőrzött videókat. Ha leáll, a halott videók a katalógusban maradnak.",
"explore_cleanup": "A türelmi idő után eltávolítja a felfedezett, de nem követett csatornákat (és videóikat), hogy a kíváncsiság-böngészés ne halmozódjon a katalógusban. Ha leáll, az elhagyott felfedezett csatornák megmaradnak."
},
"jobs": {
"rss_poll": "RSS-lekérdezés (új feltöltések)",
@ -77,7 +78,8 @@
"subscriptions": "Feliratkozások újraszinkronja",
"playlist_sync": "YouTube lejátszási listák szinkronja",
"maintenance": "Karbantartás + ellenőrzés",
"demo_reset": "Demo fiók visszaállítása"
"demo_reset": "Demo fiók visszaállítása",
"explore_cleanup": "Felfedezett csatornák takarítása"
},
"queue": {
"recentPending": "Szinkronra váró csatorna",