diff options
60 files changed, 2208 insertions, 1068 deletions
@@ -5,7 +5,7 @@ RELEASE := 1 STATIC := 0 -DISABLE_LSQUIC := 0 +DISABLE_QUIC := 0 NO_DBG_SYMBOLS := 0 @@ -27,8 +27,8 @@ else FLAGS += --debug endif -ifeq ($(DISABLE_LSQUIC), 1) - FLAGS += -Ddisable_lsquic +ifeq ($(DISABLE_QUIC), 1) + FLAGS += -Ddisable_quic endif @@ -108,7 +108,7 @@ help: echo "RELEASE Make a release build (Default: 1)" echo "STATIC Link librariess tatically (Default: 1)" echo "" - echo "DISABLE_LSQUIC Don't use lsquic (Default: 0)" + echo "DISABLE_QUIC Disable support for QUIC (Default: 0)" echo "NO_DBG_SYMBOLS Strip debug symbols (Default: 0)" @@ -44,6 +44,10 @@ <a href="https://web.libera.chat/?channel=#invidious"> <img alt="Libera.chat (IRC)" src="https://img.shields.io/badge/IRC%20%28Libera.chat%29-%23invidious-darkgreen"> </a> + <br> + <a rel="me" href="https://social.tchncs.de/@invidious"> + <img alt="Mastodon: @invidious@social.tchncs.de" src="https://img.shields.io/badge/Mastodon-%40invidious%40social.tchncs.de-darkgreen"> + </a> </div> diff --git a/assets/js/player.js b/assets/js/player.js index a461c53d..5ff55eb3 100644 --- a/assets/js/player.js +++ b/assets/js/player.js @@ -38,6 +38,8 @@ embed_url.searchParams.delete('v'); short_url = location.origin + '/' + video_data.id + embed_url.search; embed_url = location.origin + '/embed/' + video_data.id + embed_url.search; +var save_player_pos_key = "save_player_pos"; + var shareOptions = { socials: ['fbFeed', 'tw', 'reddit', 'email'], @@ -57,6 +59,16 @@ videojs.Hls.xhr.beforeRequest = function(options) { var player = videojs('player', options); +const storage = (() => { + try { + if (localStorage.length !== -1) { + return localStorage; + } + } catch (e) { + console.info('No storage available: ' + e); + } + return undefined; +})(); if (location.pathname.startsWith('/embed/')) { player.overlay({ @@ -199,6 +211,32 @@ if (video_data.premiere_timestamp && Math.round(new Date() / 1000) < video_data. player.getChild('bigPlayButton').hide(); } +if (video_data.params.save_player_pos) { + const url = new URL(location); + const hasTimeParam = url.searchParams.has("t"); + const remeberedTime = get_video_time(); + let lastUpdated = 0; + + if(!hasTimeParam) { + set_seconds_after_start(remeberedTime); + } + + const updateTime = () => { + const raw = player.currentTime(); + const time = Math.floor(raw); + + if(lastUpdated !== time && raw <= video_data.length_seconds - 15) { + save_video_time(time); + lastUpdated = time; + } + }; + + player.on("timeupdate", updateTime); +} +else { + remove_all_video_times(); +} + if (video_data.params.autoplay) { var bpb = player.getChild('bigPlayButton'); bpb.hide(); @@ -330,6 +368,65 @@ function skip_seconds(delta) { player.currentTime(newTime); } +function set_seconds_after_start(delta) { + const start = video_data.params.video_start; + player.currentTime(start + delta); +} + +function save_video_time(seconds) { + const videoId = video_data.id; + const all_video_times = get_all_video_times(); + + all_video_times[videoId] = seconds; + + set_all_video_times(all_video_times); +} + +function get_video_time() { + try { + const videoId = video_data.id; + const all_video_times = get_all_video_times(); + const timestamp = all_video_times[videoId]; + + return timestamp || 0; + } + catch { + return 0; + } +} + +function set_all_video_times(times) { + if (storage) { + if (times) { + try { + storage.setItem(save_player_pos_key, JSON.stringify(times)); + } catch (e) { + console.debug('set_all_video_times: ' + e); + } + } else { + storage.removeItem(save_player_pos_key); + } + } +} + +function get_all_video_times() { + if (storage) { + const raw = storage.getItem(save_player_pos_key); + if (raw !== null) { + try { + return JSON.parse(raw); + } catch (e) { + console.debug('get_all_video_times: ' + e); + } + } + } + return {}; +} + +function remove_all_video_times() { + set_all_video_times(null); +} + function set_time_percent(percent) { const duration = player.duration(); const newTime = duration * (percent / 100); diff --git a/assets/js/themes.js b/assets/js/themes.js index 543b849e..470f10bf 100644 --- a/assets/js/themes.js +++ b/assets/js/themes.js @@ -11,7 +11,9 @@ toggle_theme.addEventListener('click', function () { xhr.open('GET', url, true); set_mode(dark_mode); - window.localStorage.setItem('dark_mode', dark_mode ? 'dark' : 'light'); + try { + window.localStorage.setItem('dark_mode', dark_mode ? 'dark' : 'light'); + } catch {} xhr.send(); }); @@ -23,9 +25,12 @@ window.addEventListener('storage', function (e) { }); window.addEventListener('DOMContentLoaded', function () { - window.localStorage.setItem('dark_mode', document.getElementById('dark_mode_pref').textContent); - // Update localStorage if dark mode preference changed on preferences page - update_mode(window.localStorage.dark_mode); + const dark_mode = document.getElementById('dark_mode_pref').textContent; + try { + // Update localStorage if dark mode preference changed on preferences page + window.localStorage.setItem('dark_mode', dark_mode); + } catch {} + update_mode(dark_mode); }); @@ -37,9 +42,11 @@ lightScheme.addListener(scheme_switch); function scheme_switch (e) { // ignore this method if we have a preference set - if (localStorage.getItem('dark_mode')) { - return; - } + try { + if (localStorage.getItem('dark_mode')) { + return; + } + } catch {} if (e.matches) { if (e.media.includes("dark")) { set_mode(true); diff --git a/docker/Dockerfile b/docker/Dockerfile index 5f1c0a11..c73821da 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -18,7 +18,7 @@ COPY ./.git/ ./.git/ RUN crystal spec --warnings all \ --link-flags "-lxml2 -llzma" -RUN if [ ${release} == 1 ] ; then \ +RUN if [ "${release}" == 1 ] ; then \ crystal build ./src/invidious.cr \ --release \ --static --warnings all \ diff --git a/locales/ar.json b/locales/ar.json index f2d457b6..3e9d3ac6 100644 --- a/locales/ar.json +++ b/locales/ar.json @@ -433,5 +433,34 @@ "footer_documentation": "التوثيق", "footer_donate_page": "تبرّع", "preferences_region_label": "بلد المحتوى:. ", - "preferences_quality_dash_label": "جودة الفيديو المفضلة dash: " + "preferences_quality_dash_label": "جودة فيديو DASH المفضلة: ", + "preferences_quality_option_dash": "DASH (جودة تكييفية)", + "preferences_quality_option_hd720": "HD720", + "preferences_quality_option_medium": "متوسطة", + "preferences_quality_option_small": "صغيرة", + "preferences_quality_dash_option_auto": "تلقائي", + "preferences_quality_dash_option_best": "الأفضل", + "preferences_quality_dash_option_worst": "أسوأ", + "preferences_quality_dash_option_4320p": "4320p", + "preferences_quality_dash_option_2160p": "2160p", + "preferences_quality_dash_option_1440p": "1440p", + "preferences_quality_dash_option_1080p": "1080p", + "preferences_quality_dash_option_720p": "720p", + "preferences_quality_dash_option_480p": "480p", + "preferences_quality_dash_option_360p": "360p", + "preferences_quality_dash_option_240p": "240p", + "preferences_quality_dash_option_144p": "144p", + "purchased": "تم شراؤها", + "none": "لاشيء", + "videoinfo_started_streaming_x_ago": "بدأ البث منذ `x`", + "videoinfo_watch_on_youTube": "مشاهدة على يوتيوب", + "videoinfo_youTube_embed_link": "مضمن", + "videoinfo_invidious_embed_link": "رابط مضمن", + "user_created_playlists": "'x' إنشاء قوائم التشغيل", + "user_saved_playlists": "قوائم التشغيل المحفوظة 'x'", + "Video unavailable": "الفيديو غير متوفر", + "360": "360°", + "download_subtitles": "ترجمات - 'x' (.vtt)", + "invidious": "الخيالي", + "preferences_save_player_pos_label": "احفظ وقت الفيديو الحالي: " } diff --git a/locales/da.json b/locales/da.json index 72e282bb..c08984d9 100644 --- a/locales/da.json +++ b/locales/da.json @@ -11,7 +11,7 @@ "([^.,0-9]|^)1([^.,0-9]|$)": "`x` afspilningslister", "": "`x` afspilningslister" }, - "LIVE": "DIREKTE", + "LIVE": "LIVE", "Shared `x` ago": "Delt for `x` siden", "Unsubscribe": "Opsig abonnement", "Subscribe": "Abonner", @@ -44,7 +44,7 @@ "Export data as JSON": "Exporter data som JSON", "Delete account?": "Slet konto?", "History": "Historik", - "An alternative front-end to YouTube": "En alternativ forside til YouTube", + "An alternative front-end to YouTube": "Et alternativt front-end til YouTube", "JavaScript license information": "JavaScript licens information", "source": "kilde", "Log in": "Log på", @@ -72,7 +72,7 @@ "preferences_volume_label": "Lydstyrke: ", "preferences_comments_label": "Standard kommentarer: ", "youtube": "YouTube", - "reddit": "reddit", + "reddit": "Reddit", "preferences_captions_label": "Standard undertekster: ", "Fallback captions: ": "Alternative undertekster: ", "preferences_related_videos_label": "Vis relaterede videoer: ", @@ -150,8 +150,8 @@ "Unlisted": "Skjult", "Private": "Privat", "View all playlists": "Vis alle afspilningslister", - "Updated `x` ago": "Opdateret for 'x' siden", - "Delete playlist `x`?": "Opdateret `x` siden", + "Updated `x` ago": "Opdateret for `x` siden", + "Delete playlist `x`?": "Fjern spilleliste `x`?", "Delete playlist": "Slet afspilningsliste", "Create playlist": "Opret afspilningsliste", "Title": "Titel", @@ -176,7 +176,7 @@ }, "Premieres in `x`": "Har premiere om `x`", "Premieres `x`": "Har premiere om `x`", - "Hi! Looks like you have JavaScript turned off. Click here to view comments, keep in mind they may take a bit longer to load.": "Hej! Det ser ud til at du har JavaScript slået fra. Klik her for at se kommentarer, vær opmærksom på at de kan tage længere om at loade.", + "Hi! Looks like you have JavaScript turned off. Click here to view comments, keep in mind they may take a bit longer to load.": "Hej! Det ser ud til at du har JavaScript slået fra. Klik her for at se kommentarer, vær opmærksom på at de kan tage længere om at indlæse.", "View YouTube comments": "Vis YouTube kommentarer", "View more comments on Reddit": "Se flere kommentarer på Reddit", "View `x` comments": { @@ -190,17 +190,17 @@ "Quota exceeded, try again in a few hours": "Kvota overskredet, prøv igen om et par timer", "Unable to log in, make sure two-factor authentication (Authenticator or SMS) is turned on.": "Login fejlet, tjek at totrinsbekræftelse (Authenticator eller SMS) er slået til.", "Invalid TFA code": "Ugyldig TFA kode", - "Login failed. This may be because two-factor authentication is not turned on for your account.": "Login fejlede. Det er måske på grund af to-faktor-autentisering ikk er slået til for din konto.", + "Login failed. This may be because two-factor authentication is not turned on for your account.": "Login fejlede. Dette kan skyldes, at to-faktor autentificering ikke er aktiveret for din konto.", "Wrong answer": "Forkert svar", "Erroneous CAPTCHA": "Fejlagtig CAPTCHA", - "CAPTCHA is a required field": "CAPTCHA er et krævet felt", + "CAPTCHA is a required field": "CAPTCHA er et obligatorisk felt", "User ID is a required field": "Bruger ID er et krævet felt", - "Password is a required field": "Adgangskode er et krævet felt", + "Password is a required field": "Adgangskode er et obligatorisk felt", "Wrong username or password": "Forkert brugernavn eller adgangskode", - "Please sign in using 'Log in with Google'": "Venligst tjek in via 'Log in med Google'", - "Password cannot be empty": "Adgangskode kan ikke være tom", + "Please sign in using 'Log in with Google'": "Log ind via 'Log ind med Google'", + "Password cannot be empty": "Adgangskoden må ikke være tom", "Password cannot be longer than 55 characters": "Adgangskoden må ikke være længere end 55 tegn", - "Please log in": "Venligst log in", + "Please log in": "Venligst log ind", "channel:`x`": "kanal: 'x'", "Deleted or invalid channel": "Slettet eller invalid kanal", "This channel does not exist.": "Denne kanal eksisterer ikke.", @@ -219,5 +219,248 @@ "Could not create mix.": "Kunne ikke skabe blanding.", "Empty playlist": "Tom playliste", "Not a playlist.": "Ikke en playliste.", - "Playlist does not exist.": "Playlist eksisterer ikke." + "Playlist does not exist.": "Playlist eksisterer ikke.", + "Esperanto": "Esperanto", + "Czech": "Tjekkisk", + "Danish": "Dansk", + "Community": "Samfund", + "Afrikaans": "Afrikansk", + "Portuguese": "Portugisisk", + "Ukrainian": "Ukrainsk", + "Fallback comments: ": "Fallback kommentarer: ", + "Popular": "Populær", + "footer_donate_page": "Doner", + "Gujarati": "Gujarati", + "Punjabi": "Punjabi", + "Sundanese": "Sundanesisk", + "Urdu": "Urdu", + "preferences_region_label": "Indhold land: ", + "Hidden field \"challenge\" is a required field": "Det skjulte felt \"challenge\" er et påkrævet felt", + "Albanian": "Albansk", + "preferences_quality_dash_label": "Fortrukket DASH video kvalitet: ", + "live": "Direkte", + "Lao": "Lao-tse", + "Filipino": "Filippinsk", + "Greek": "Græsk", + "Kurdish": "Kurdisk", + "Malay": "Malaysisk", + "Romanian": "Rumænsk", + "Somali": "Somalisk", + "`x` years": { + "": "`x`år", + "([^.,0-9]|^)1([^.,0-9]|$)": "`x` år" + }, + "preferences_locale_label": "Sprog: ", + "News": "Nyheder", + "permalink": "permalink", + "date": "Upload dato", + "features": "Funktioner", + "filter": "Filter", + "Khmer": "Khmer", + "Finnish": "Finsk", + "week": "Denne uge", + "Korean": "Koreansk", + "Telugu": "Telugu", + "Malayalam": "Malayalam", + "View as playlist": "Se som spilleliste", + "Hungarian": "Ungarsk", + "Welsh": "Walisisk", + "subtitles": "Undertekster/CC", + "Bosnian": "Bosnisk", + "Yiddish": "Jiddisch", + "Belarusian": "Belarussisk", + "today": "I dag", + "Shona": "Shona", + "Slovenian": "Slovensk", + "Gaming": "Gaming", + "Bangla": "Bengali", + "Swahili": "Swahili", + "`x` marked it with a ❤": "`x`markeret med et ❤", + "Kyrgyz": "Kirgisisk", + "Turkish": "Tyrkisk", + "adminprefs_modified_source_code_url_label": "URL-adresse til modificeret kildekodelager", + "Switch Invidious Instance": "Skift Invidious instans", + "Samoan": "Samoansk", + "Spanish": "Spansk", + "%A %B %-d, %Y": "%A %B %-d, %Y", + "footer_documentation": "Dokumentation", + "Pashto": "Pashto", + "footer_modfied_source_code": "Modificeret Kildekode", + "Released under the AGPLv3 on Github.": "Udgivet under AGPLv3 på Github.", + "Tajik": "Tadsjikisk", + "`x` months": { + "": "`x`måneder", + "([^.,0-9]|^)1([^.,0-9]|$)": "`x`måned" + }, + "month": "Denne måned", + "Hebrew": "Hebraisk", + "Kannada": "Kannada", + "`x` weeks": { + "": "`x`uger", + "([^.,0-9]|^)1([^.,0-9]|$)": "`x`uge" + }, + "Current version: ": "Nuværende version: ", + "Amharic": "Amharisk", + "`x` days": { + "([^.,0-9]|^)1([^.,0-9]|$)": "`x`dag", + "": "`x`dage" + }, + "Swedish": "Svensk", + "Corsican": "Korsikansk", + "movie": "Film", + "Could not pull trending pages.": "Kunne ikke hente trending sider.", + "English": "Engelsk", + "hd": "HD", + "Hausa": "Islandsk", + "year": "Dette år", + "Japanese": "Japansk", + "content_type": "Type", + "Icelandic": "Islandsk", + "Basque": "Baskisk", + "rating": "Bedømmelse", + "Yoruba": "Yoruba", + "Erroneous token": "Fejlagtig token", + "Videos": "Videoer", + "show": "Vis", + "Luxembourgish": "Luxemboursk", + "Vietnamese": "Vietnamesisk", + "Latvian": "Lettisk", + "Indonesian": "Indonesisk", + "duration": "Varighed", + "footer_original_source_code": "Original kildekode", + "Search": "Søg", + "Serbian": "Serbisk", + "Armenian": "Armensk", + "Bulgarian": "Bulgarsk", + "French": "Fransk", + "Burmese": "Burmesisk", + "Macedonian": "Makedonsk", + "Southern Sotho": "Sydlige Sotho", + "About": "Omkring", + "Malagasy": "Madagaskiske", + "Rating: ": "Bedømmelse: ", + "Movies": "Film", + "YouTube comment permalink": "Youtube kommentarer permalink", + "location": "Lokation", + "hdr": "HDR", + "Cebuano": "Cebuano (Sugbuanon)", + "Nyanja": "Nyanja", + "Chinese (Simplified)": "Kinesisk (forenklet)", + "Chinese (Traditional)": "Kinesisk (traditionelt)", + "Dutch": "Hollandsk", + "Estonian": "Estisk", + "preferences_automatic_instance_redirect_label": "Automatisk eksempel omdirigering (Fallback til redirect.invidious.io): ", + "Nepali": "Nepalesisk", + "Norwegian Bokmål": "Norsk Bokmål", + "`x` hours": { + "": "`x` timer", + "([^.,0-9]|^)1([^.,0-9]|$)": "`x`time" + }, + "`x` minutes": { + "([^.,0-9]|^)1([^.,0-9]|$)": "`x` minut", + "": "`x` minuter" + }, + "`x` seconds": { + "([^.,0-9]|^)1([^.,0-9]|$)": "`x` sekund", + "": "`x` sekunder" + }, + "(edited)": "(ændret)", + "preferences_show_nick_label": "Vis kælenavn på toppen: ", + "Galician": "Galisisk", + "German": "Tysk", + "Maori": "Maori", + "Slovak": "Slovakisk", + "relevance": "Relevans", + "hour": "Sidste time", + "playlist": "Spilleliste", + "long": "Lang (> 20 minutter)", + "creative_commons": "Creative Commons", + "Marathi": "Marathi", + "Sindhi": "Sindhi", + "preferences_category_misc": "Diverse indstillinger", + "Erroneous challenge": "Fejlagtig udfordring", + "Hindi": "Hindi", + "Igbo": "Igbo", + "Javanese": "Javanesisk", + "Kazakh": "Kasabhisk", + "Latin": "Latinsk", + "Lithuanian": "Lituaisk", + "Mongolian": "Mongolsk", + "Spanish (Latin America)": "Spansk (Latinamerika)", + "Uzbek": "Usbekisk", + "Western Frisian": "Vestfrisisk", + "Top": "Top", + "Music": "Musik", + "views": "Antal visninger", + "sort": "Sorter efter", + "Zulu": "Zulu", + "Invidious Private Feed for `x`": "Invidious Privat Feed til `x`", + "English (auto-generated)": "Engelsk (autogenereret)", + "Arabic": "Arabisk", + "Croatian": "Kroatisk", + "Hawaiian": "Hawaiiansk", + "Maltese": "Maltesisk", + "Polish": "Polsk", + "Russian": "Russisk", + "Download": "Hent", + "Download as: ": "Hent som: ", + "Playlists": "Spillelister", + "next_steps_error_message_refresh": "Opdater", + "next_steps_error_message_go_to_youtube": "Gå til Youtube", + "footer_source_code": "Kildekode", + "Tamil": "Tamil", + "Xhosa": "Xhosa", + "next_steps_error_message": "Efter det burde du prøve at: ", + "Sinhala": "Singalesisk (Sinhala)", + "Thai": "Thai", + "Broken? Try another Invidious Instance": "I stykker? Prøv en anden Invidious instans", + "No such user": "Brugeren findes ikke", + "Token is expired, please try again": "Token er udløbet, prøv igen", + "Catalan": "Catalansk", + "Haitian Creole": "Haitiansk", + "Irish": "Irsk", + "Persian": "Persisk", + "Scottish Gaelic": "Skotsk Gælisk", + "Default": "Standard", + "Video mode": "Videotilstand", + "short": "Kort (< 4 minutter)", + "Hidden field \"token\" is a required field": "Det skjulte felt \"token\" er et påkrævet felt", + "Azerbaijani": "Aserbajdsjansk", + "Georgian": "Georgisk", + "Italian": "Italiensk", + "Audio mode": "Lydtilstand", + "video": "Video", + "channel": "Kanal", + "3d": "3D", + "4k": "4K", + "Hmong": "Hmong", + "preferences_quality_option_medium": "Medium", + "preferences_quality_option_small": "Lille", + "preferences_quality_dash_option_best": "Bedste", + "preferences_quality_dash_option_worst": "Værste", + "preferences_quality_dash_option_4320p": "4320p", + "preferences_quality_dash_option_1080p": "1080p", + "preferences_quality_dash_option_720p": "720p", + "preferences_quality_dash_option_480p": "480p", + "preferences_quality_dash_option_360p": "360p", + "preferences_quality_dash_option_144p": "144p", + "invidious": "Invidious", + "purchased": "Købt", + "360": "360°", + "none": "ingen", + "videoinfo_started_streaming_x_ago": "Streamen blev startet for `x`siden", + "videoinfo_watch_on_youTube": "Se på YouTube", + "videoinfo_youTube_embed_link": "Integrer", + "videoinfo_invidious_embed_link": "Integrer Link", + "download_subtitles": "Undertekster - `x`(.vtt)", + "user_created_playlists": "`x`opretede spillelister", + "user_saved_playlists": "´x`gemte spillelister", + "Video unavailable": "Video ikke tilgængelig", + "preferences_save_player_pos_label": "Gem den nuværende videotid: ", + "preferences_quality_dash_option_auto": "Auto", + "preferences_quality_option_hd720": "HD720", + "preferences_quality_dash_option_2160p": "2160p", + "preferences_quality_option_dash": "DASH (adaptiv kvalitet)", + "preferences_quality_dash_option_1440p": "1440p", + "preferences_quality_dash_option_240p": "240p" } diff --git a/locales/de.json b/locales/de.json index 7ec11fef..9ab5b211 100644 --- a/locales/de.json +++ b/locales/de.json @@ -19,8 +19,8 @@ "View playlist on YouTube": "Wiedergabeliste auf YouTube anzeigen", "newest": "neueste", "oldest": "älteste", - "popular": "beliebt", - "last": "letzte", + "popular": "beliebteste", + "last": "neueste", "Next page": "Nächste Seite", "Previous page": "Vorherige Seite", "Clear watch history?": "Verlauf löschen?", @@ -72,7 +72,7 @@ "preferences_volume_label": "Wiedergabelautstärke: ", "preferences_comments_label": "Standardkommentare: ", "youtube": "YouTube", - "reddit": "reddit", + "reddit": "Reddit", "preferences_captions_label": "Standarduntertitel: ", "Fallback captions: ": "Ersatzuntertitel: ", "preferences_related_videos_label": "Ähnliche Videos anzeigen? ", @@ -84,7 +84,7 @@ "Dark mode: ": "Nachtmodus: ", "preferences_dark_mode_label": "Modus: ", "dark": "Nachtmodus", - "light": "heller Modus", + "light": "hell", "preferences_thin_mode_label": "Schlanker Modus: ", "preferences_category_misc": "Sonstige Einstellungen", "preferences_automatic_instance_redirect_label": "Automatische Instanzweiterleitung (über redirect.invidious.io): ", @@ -149,7 +149,7 @@ "Source available here.": "Quellcode verfügbar hier.", "View JavaScript license information.": "Javascript Lizenzinformationen anzeigen.", "View privacy policy.": "Datenschutzerklärung einsehen.", - "Trending": "Trending", + "Trending": "Angesagt", "Public": "Öffentlich", "Unlisted": "Nicht aufgeführt", "Private": "Privat", @@ -422,7 +422,7 @@ "filter": "Filtern", "Current version: ": "Aktuelle Version: ", "next_steps_error_message": "Danach folgendes versuchen: ", - "next_steps_error_message_refresh": "Neuladen", + "next_steps_error_message_refresh": "Aktualisieren", "next_steps_error_message_go_to_youtube": "Zu YouTube gehen", "footer_donate_page": "Spende", "long": "Lang (> 20 Minuten)", @@ -431,5 +431,36 @@ "footer_documentation": "Dokumentation", "footer_source_code": "Quellcode", "adminprefs_modified_source_code_url_label": "URL zum Repositorie des modifizierten Quellcodes", - "short": "Kurz (< 4 Minuten)" + "short": "Kurz (< 4 Minuten)", + "preferences_region_label": "Land der Inhalte: ", + "preferences_quality_option_dash": "DASH (automatische Qualität)", + "preferences_quality_option_hd720": "HD720", + "preferences_quality_option_medium": "Mittel", + "preferences_quality_option_small": "Niedrig", + "preferences_quality_dash_option_auto": "Auto", + "preferences_quality_dash_option_4320p": "4320p", + "preferences_quality_dash_option_2160p": "2160p", + "preferences_quality_dash_option_1080p": "1080p", + "preferences_quality_dash_option_720p": "720p", + "preferences_quality_dash_option_480p": "480p", + "preferences_quality_dash_option_360p": "360p", + "preferences_quality_dash_option_240p": "240p", + "preferences_quality_dash_option_144p": "144p", + "invidious": "Invidious", + "videoinfo_invidious_embed_link": "Link zum Einbetten", + "download_subtitles": "Untertitel - `x` (.vtt)", + "Video unavailable": "Video nicht verfügbar", + "user_created_playlists": "`x` Wiedergabelisten erstellt", + "user_saved_playlists": "`x` Wiedergabelisten gespeichert", + "preferences_save_player_pos_label": "Aktuelle Position speichern: ", + "360": "360°", + "preferences_quality_dash_option_best": "Höchste", + "preferences_quality_dash_option_worst": "Niedrigste", + "preferences_quality_dash_option_1440p": "1440p", + "videoinfo_youTube_embed_link": "Eingebettet", + "purchased": "Gekauft", + "none": "keine", + "videoinfo_started_streaming_x_ago": "Stream begann vor `x`", + "videoinfo_watch_on_youTube": "Auf YouTube ansehen", + "preferences_quality_dash_label": "Bevorzugte DASH-Videoqualität: " } diff --git a/locales/en-US.json b/locales/en-US.json index 036c22f4..94aac89e 100644 --- a/locales/en-US.json +++ b/locales/en-US.json @@ -69,11 +69,28 @@ "preferences_local_label": "Proxy videos: ", "preferences_speed_label": "Default speed: ", "preferences_quality_label": "Preferred video quality: ", - "preferences_quality_dash_label": "Preferred dash video quality: ", + "preferences_quality_option_dash": "DASH (adaptative quality)", + "preferences_quality_option_hd720": "HD720", + "preferences_quality_option_medium": "Medium", + "preferences_quality_option_small": "Small", + "preferences_quality_dash_label": "Preferred DASH video quality: ", + "preferences_quality_dash_option_auto": "Auto", + "preferences_quality_dash_option_best": "Best", + "preferences_quality_dash_option_worst": "Worst", + "preferences_quality_dash_option_4320p": "4320p", + "preferences_quality_dash_option_2160p": "2160p", + "preferences_quality_dash_option_1440p": "1440p", + "preferences_quality_dash_option_1080p": "1080p", + "preferences_quality_dash_option_720p": "720p", + "preferences_quality_dash_option_480p": "480p", + "preferences_quality_dash_option_360p": "360p", + "preferences_quality_dash_option_240p": "240p", + "preferences_quality_dash_option_144p": "144p", "preferences_volume_label": "Player volume: ", "preferences_comments_label": "Default comments: ", "youtube": "YouTube", "reddit": "Reddit", + "invidious": "Invidious", "preferences_captions_label": "Default captions: ", "Fallback captions: ": "Fallback captions: ", "preferences_related_videos_label": "Show related videos: ", @@ -89,7 +106,7 @@ "light": "light", "preferences_thin_mode_label": "Thin mode: ", "preferences_category_misc": "Miscellaneous preferences", - "preferences_automatic_instance_redirect_label": "Automaticatic instance redirection (fallback to redirect.invidious.io): ", + "preferences_automatic_instance_redirect_label": "Automatic instance redirection (fallback to redirect.invidious.io): ", "preferences_category_subscription": "Subscription preferences", "preferences_annotations_subscribed_label": "Show annotations by default for subscribed channels? ", "Redirect homepage to feed: ": "Redirect homepage to feed: ", @@ -423,6 +440,8 @@ "4k": "4K", "location": "Location", "hdr": "HDR", + "purchased" : "Purchased", + "360" : "360°", "filter": "Filter", "Current version: ": "Current version: ", "next_steps_error_message": "After which you should try to: ", @@ -433,5 +452,15 @@ "footer_source_code": "Source code", "footer_original_source_code": "Original source code", "footer_modfied_source_code": "Modified Source code", - "adminprefs_modified_source_code_url_label": "URL to modified source code repository" + "adminprefs_modified_source_code_url_label": "URL to modified source code repository", + "none": "none", + "videoinfo_started_streaming_x_ago": "Started streaming `x` ago", + "videoinfo_watch_on_youTube": "Watch on YouTube", + "videoinfo_youTube_embed_link": "Embed", + "videoinfo_invidious_embed_link": "Embed Link", + "download_subtitles": "Subtitles - `x` (.vtt)", + "user_created_playlists": "`x` created playlists", + "user_saved_playlists": "`x` saved playlists", + "Video unavailable": "Video unavailable", + "preferences_save_player_pos_label": "Save the current video time: " } diff --git a/locales/es.json b/locales/es.json index 1403a731..9f876ccb 100644 --- a/locales/es.json +++ b/locales/es.json @@ -433,5 +433,34 @@ "footer_modfied_source_code": "Código fuente modificado", "footer_donate_page": "Donar", "preferences_region_label": "País del contenido: ", - "preferences_quality_dash_label": "Calidad de vídeo DASH preferida: " + "preferences_quality_dash_label": "Calidad de vídeo DASH preferida: ", + "preferences_quality_option_hd720": "HD720", + "preferences_quality_option_medium": "Intermedia", + "preferences_quality_dash_option_auto": "Automática", + "none": "ninguno", + "videoinfo_started_streaming_x_ago": "Comenzó difusión hace `x`", + "download_subtitles": "Subtítulos- `x` (.vtt)", + "user_created_playlists": "`x` listas de reproducción creadas", + "user_saved_playlists": "`x` listas de reproducción guardadas", + "Video unavailable": "Vídeo no disponible", + "videoinfo_youTube_embed_link": "Embeber", + "preferences_quality_dash_option_2160p": "2160p", + "preferences_quality_dash_option_4320p": "4320p", + "invidious": "Invidious", + "preferences_quality_dash_option_480p": "480p", + "preferences_quality_option_dash": "DASH (calidad adaptativa)", + "preferences_quality_dash_option_720p": "720p", + "preferences_quality_dash_option_360p": "360p", + "preferences_quality_dash_option_240p": "240p", + "preferences_quality_dash_option_144p": "144p", + "preferences_quality_option_small": "Pequeña", + "preferences_quality_dash_option_1440p": "1440p", + "preferences_quality_dash_option_best": "La mejor", + "preferences_quality_dash_option_worst": "La peor", + "videoinfo_invidious_embed_link": "Enlace para Embeber", + "preferences_quality_dash_option_1080p": "1080p", + "purchased": "Comprado", + "360": "360°", + "videoinfo_watch_on_youTube": "Ver en YouTube", + "preferences_save_player_pos_label": "Guardar el tiempo del vídeo actual: " } diff --git a/locales/fa.json b/locales/fa.json index efee1cdb..1f723a63 100644 --- a/locales/fa.json +++ b/locales/fa.json @@ -1,51 +1,51 @@ { "`x` subscribers": { - "([^.,0-9]|^)1([^.,0-9]|$)": "`x` مشترکان", - "": "`x` مشترکان" + "([^.,0-9]|^)1([^.,0-9]|$)": "`x` دنبال کننده", + "": "`x` دنبال کننده" }, "`x` videos": { - "([^.,0-9]|^)1([^.,0-9]|$)": "`x` ویدیو ها", - "": "`x` ویدیو ها" + "([^.,0-9]|^)1([^.,0-9]|$)": "`x` ویدئو", + "": "`x` ویدئو" }, "`x` playlists": { - "([^.,0-9]|^)1([^.,0-9]|$)": "`x` سیاههٔ پخش", - "": "`x` سیاهههای پخش" + "([^.,0-9]|^)1([^.,0-9]|$)": "`x` فهرست پخش", + "": "`x` فهرست پخش" }, "LIVE": "زنده", - "Shared `x` ago": "به اشتراک گذاشته شده `x` پیش", + "Shared `x` ago": "`x` پیش به اشتراک گذاشته شده", "Unsubscribe": "لغو اشتراک", "Subscribe": "مشترک شدن", - "View channel on YouTube": "نمایش کانال در یوتیوب", - "View playlist on YouTube": "نمایش سیاههٔ پخش در یوتیوب", - "newest": "جدید تر", - "oldest": "قدیمی تر", + "View channel on YouTube": "دیدن کانال در یوتیوب", + "View playlist on YouTube": "دیدن فهرست پخش در یوتیوب", + "newest": "تازهترین", + "oldest": "کهنهترین", "popular": "محبوب", "last": "آخرین", "Next page": "صفحه بعد", "Previous page": "صفحه قبل", "Clear watch history?": "پاک کردن تاریخچه نمایش؟", - "New password": "گذرواژه جدید", - "New passwords must match": "گذارواژه های جدید باید باهم همخوانی داشته باشند", + "New password": "گذرواژه تازه", + "New passwords must match": "گذارواژه های تازه باید باهم همخوانی داشته باشند", "Cannot change password for Google accounts": "نمیتوان گذرواژه را برای حساب های کاربری گوگل تغییر داد", "Authorize token?": "توکن دسترسی؟", "Authorize token for `x`?": "توکن دسترسی برای `x`؟", "Yes": "بله", "No": "خیر", - "Import and Export Data": "وارد کردن و خارج کردن داده ها", - "Import": "وارد کردن", - "Import Invidious data": "وارد کردن داده Invidious", - "Import YouTube subscriptions": "وارد کردن اشتراک های یوتیوب", - "Import FreeTube subscriptions (.db)": "وارد کردن اشتراک های فری توب (.db)", - "Import NewPipe subscriptions (.json)": "وارد کردن اشتراک های نیو پایپ (.json)", - "Import NewPipe data (.zip)": "وارد کردن داده نیو پایپ (.zip)", - "Export": "خارج کردن", - "Export subscriptions as OPML": "خارج کردن اشتراک ها به عنوان OPML", - "Export subscriptions as OPML (for NewPipe & FreeTube)": "خارج کردن اشتراک ها به عنوان OPML (برای فری توب و نیو پایپ)", - "Export data as JSON": "خارج کردن داده ها به عنوان JSON", + "Import and Export Data": "درونبرد و برونبرد داده", + "Import": "درونبرد", + "Import Invidious data": "درونبرد داده اینویدیوس", + "Import YouTube subscriptions": "درونبرد اشتراکهای یوتیوب", + "Import FreeTube subscriptions (.db)": "درونبرد اشتراکهای فریتیوب (.db)", + "Import NewPipe subscriptions (.json)": "درونبرد اشتراکهای نیوپایپ (.json)", + "Import NewPipe data (.zip)": "درونبرد داده نیوپایپ (.zip)", + "Export": "برونبرد", + "Export subscriptions as OPML": "برونبرد اشتراکها در قالب OPML", + "Export subscriptions as OPML (for NewPipe & FreeTube)": "برونبرد اشتراکها در قالب OPML (برای نیوپایپ و فریتیوب)", + "Export data as JSON": "برونبرد داده در قالب JSON", "Delete account?": "حذف حساب کاربری؟", "History": "تاریخچه", - "An alternative front-end to YouTube": "یک فرانت-اند جایگذین برای یوتیوب", - "JavaScript license information": "اطلاعات مجوز جاوا اسکریپت", + "An alternative front-end to YouTube": "یک پیشانه جایگزین برای یوتیوب", + "JavaScript license information": "اطلاعات پروانه جاوااسکریپت", "source": "منبع", "Log in": "ورود", "Log in/register": "ورود/ثبت نام", @@ -53,15 +53,15 @@ "User ID": "شناسه کاربری", "Password": "گذرواژه", "Time (h:mm:ss):": "زمان (h:mm:ss):", - "Text CAPTCHA": "متن CAPTCHA", - "Image CAPTCHA": "تصویر CAPTCHA", + "Text CAPTCHA": "کپچای متنی", + "Image CAPTCHA": "کپچای تصویری", "Sign In": "ورود", "Register": "ثبت نام", "E-mail": "ایمیل", "Google verification code": "کد تایید گوگل", "Preferences": "ترجیحات", "preferences_category_player": "ترجیحات نمایشدهنده", - "preferences_video_loop_label": "همیشه تکرار شنوده: ", + "preferences_video_loop_label": "همواره ویدئو را بازپخش کن ", "preferences_autoplay_label": "نمایش خودکار: ", "preferences_continue_label": "پخش بعدی به طور پیشفرض: ", "preferences_continue_autoplay_label": "پخش خودکار ویدیو بعدی: ", @@ -423,5 +423,42 @@ "Current version: ": "نسخه فعلی: ", "next_steps_error_message": "اکنون بایستی یکی از این موارد را امتحان کنید: ", "next_steps_error_message_refresh": "تازهسازی", - "next_steps_error_message_go_to_youtube": "رفتن به یوتیوب" + "next_steps_error_message_go_to_youtube": "رفتن به یوتیوب", + "preferences_quality_option_hd720": "HD720", + "preferences_quality_option_dash": "DASH (کیفیت قابل تطبیق)", + "preferences_quality_option_medium": "میانه", + "preferences_quality_option_small": "پایین", + "preferences_quality_dash_option_auto": "خودکار", + "preferences_quality_dash_option_best": "بهترین", + "preferences_quality_dash_option_worst": "بدترین", + "preferences_quality_dash_option_4320p": "4320p", + "preferences_quality_dash_option_2160p": "2160p", + "preferences_quality_dash_option_1440p": "1440p", + "preferences_quality_dash_option_1080p": "1080p", + "preferences_quality_dash_option_720p": "720p", + "preferences_quality_dash_option_480p": "480p", + "preferences_quality_dash_option_360p": "360p", + "preferences_quality_dash_option_240p": "240p", + "preferences_quality_dash_option_144p": "144p", + "invidious": "اینویدیوس", + "360": "360°", + "footer_donate_page": "کمک مالی", + "footer_source_code": "کد منبع", + "footer_modfied_source_code": "کد منبع ویرایش شده", + "none": "هیچکدام", + "videoinfo_started_streaming_x_ago": "پخش جریانی `x` پیش آغاز شد", + "videoinfo_watch_on_youTube": "تماشا در یوتیوب", + "videoinfo_youTube_embed_link": "توکار", + "videoinfo_invidious_embed_link": "پیوند توکار", + "download_subtitles": "زیرنویسها - `x` (.vtt)", + "Video unavailable": "ویدئو دردسترس نیست", + "preferences_save_player_pos_label": "ذخیره زمان کنونی ویدئو: ", + "purchased": "خریداری شده", + "preferences_quality_dash_label": "کیفیت ترجیحی ویدئو DASH: ", + "preferences_region_label": "کشور محتوا: ", + "footer_documentation": "مستندات", + "footer_original_source_code": "کد منبع اصلی", + "long": "بلند (> 20 دقیقه)", + "adminprefs_modified_source_code_url_label": "URL مخزن کد منبع ویریش شده", + "short": "کوتاه (< 4 دقیقه)" } diff --git a/locales/fr.json b/locales/fr.json index 00971576..5ebd6f70 100644 --- a/locales/fr.json +++ b/locales/fr.json @@ -87,7 +87,7 @@ "light": "clair", "preferences_thin_mode_label": "Mode léger : ", "preferences_category_misc": "Paramètres divers", - "preferences_automatic_instance_redirect_label": "Redirection vers une autre instance automatique (via redirect.invidious.io) : ", + "preferences_automatic_instance_redirect_label": "Redirection automatique vers une autre instance (via redirect.invidious.io) : ", "preferences_category_subscription": "Préférences des abonnements", "preferences_annotations_subscribed_label": "Afficher les annotations par défaut sur les chaînes auxquelles vous êtes abonnés : ", "Redirect homepage to feed: ": "Rediriger la page d'accueil vers la page d'abonnements : ", @@ -424,7 +424,7 @@ "next_steps_error_message": "Vous pouvez essayer de : ", "next_steps_error_message_refresh": "Rafraîchir la page", "next_steps_error_message_go_to_youtube": "Aller sur YouTube", - "preferences_quality_dash_label": "Qualité préférée de la vidéo du tableau de bord : ", + "preferences_quality_dash_label": "Qualité vidéo DASH préférée : ", "footer_source_code": "Code source", "preferences_region_label": "Pays du contenu : ", "footer_donate_page": "Faire un don", @@ -433,5 +433,34 @@ "long": "Longue (> 20 minutes)", "adminprefs_modified_source_code_url_label": "URL du dépôt du code source modifié", "footer_documentation": "Documentation", - "footer_original_source_code": "Code source original" + "footer_original_source_code": "Code source original", + "preferences_quality_option_medium": "Moyenne", + "preferences_quality_option_small": "Petite", + "preferences_quality_dash_option_auto": "Auto", + "preferences_quality_dash_option_best": "La plus haute", + "preferences_quality_dash_option_worst": "La plus basse", + "preferences_quality_dash_option_4320p": "4320p", + "preferences_quality_dash_option_2160p": "2160p", + "preferences_quality_dash_option_720p": "720p", + "preferences_quality_dash_option_480p": "480p", + "preferences_quality_dash_option_360p": "360p", + "preferences_quality_dash_option_240p": "240p", + "preferences_quality_dash_option_144p": "144p", + "invidious": "Invidious", + "360": "360°", + "none": "aucun", + "videoinfo_started_streaming_x_ago": "En stream depuis `x`", + "videoinfo_watch_on_youTube": "Regarder sur YouTube", + "videoinfo_youTube_embed_link": "Intégrer", + "purchased": "Acheter", + "videoinfo_invidious_embed_link": "Lien intégré", + "download_subtitles": "Sous-titres - `x` (.vtt)", + "user_saved_playlists": "`x` listes de lecture sauvegardées", + "Video unavailable": "Vidéo non disponible", + "preferences_quality_option_hd720": "HD720", + "preferences_quality_option_dash": "DASH (qualité adaptative)", + "preferences_quality_dash_option_1440p": "1440p", + "preferences_quality_dash_option_1080p": "1080p", + "user_created_playlists": "`x` listes de lecture créées", + "preferences_save_player_pos_label": "Sauvegarder la durée actuelle de la vidéo : " } diff --git a/locales/hr.json b/locales/hr.json index 9b5f9250..02c5d784 100644 --- a/locales/hr.json +++ b/locales/hr.json @@ -8,15 +8,15 @@ "": "`x` videa" }, "`x` playlists": { - "([^.,0-9]|^)1([^.,0-9]|$)": "`x` playliste", - "": "`x` playliste" + "([^.,0-9]|^)1([^.,0-9]|$)": "`x` zbirka", + "": "`x` zbirke" }, "LIVE": "UŽIVO", "Shared `x` ago": "Dijeljeno prije `x`", "Unsubscribe": "Odjavi pretplatu", "Subscribe": "Pretplati se", "View channel on YouTube": "Prikaži kanal na YouTubeu", - "View playlist on YouTube": "Prikaži playlistu na YouTubeu", + "View playlist on YouTube": "Prikaži zbirku na YouTubeu", "newest": "najnovije", "oldest": "najstarije", "popular": "popularni", @@ -87,7 +87,7 @@ "light": "svijetlo", "preferences_thin_mode_label": "Pojednostavljen prikaz: ", "preferences_category_misc": "Razne postavke", - "preferences_automatic_instance_redirect_label": "Automatsko preusmjeravanje instance (u krajnjem slučaju koristi redirect.invidious.io): ", + "preferences_automatic_instance_redirect_label": "Automatsko preusmjeravanje instance (u krajnjem slučaju će se koristiti redirect.invidious.io): ", "preferences_category_subscription": "Postavke pretplata", "preferences_annotations_subscribed_label": "Standardno prikaži napomene za pretplaćene kanale: ", "Redirect homepage to feed: ": "Preusmjeri početnu stranicu na feed: ", @@ -153,14 +153,14 @@ "Public": "Javno", "Unlisted": "Nenavedeno", "Private": "Privatno", - "View all playlists": "Prikaži sve playliste", + "View all playlists": "Prikaži sve zbirke", "Updated `x` ago": "Aktualizirano prije `x`", - "Delete playlist `x`?": "Izbrisati playlistu `x`?", - "Delete playlist": "Izbriši playlistu", - "Create playlist": "Stvori playlistu", + "Delete playlist `x`?": "Izbrisati zbirku `x`?", + "Delete playlist": "Izbriši zbirku", + "Create playlist": "Stvori zbirku", "Title": "Naslov", - "Playlist privacy": "Privatnost playliste", - "Editing playlist `x`": "Uređivanje playliste `x`", + "Playlist privacy": "Privatnost zbirke", + "Editing playlist `x`": "Uređivanje zbirke `x`", "Show more": "Pokaži više", "Show less": "Pokaži manje", "Watch on YouTube": "Gledaj na YouTubeu", @@ -224,9 +224,9 @@ "": "`x` bodova" }, "Could not create mix.": "Neuspjelo stvaranje miksa.", - "Empty playlist": "Prazna playlista", - "Not a playlist.": "Nije playlista.", - "Playlist does not exist.": "Playlista ne postoji.", + "Empty playlist": "Prazna zbirka", + "Not a playlist.": "Nije zbirka.", + "Playlist does not exist.": "Zbirka ne postoji.", "Could not pull trending pages.": "Neuspjelo preuzimanje stranica u trendu.", "Hidden field \"challenge\" is a required field": "Skriveno polje „izazov” je obavezno polje", "Hidden field \"token\" is a required field": "Skriveno polje „token” je obavezno polje", @@ -375,7 +375,7 @@ "About": "Informacije", "Rating: ": "Ocjena: ", "preferences_locale_label": "Jezik: ", - "View as playlist": "Prikaži kao playlistu", + "View as playlist": "Prikaži kao zbirku", "Default": "Standardno", "Music": "Glazba", "Gaming": "Videoigre", @@ -383,7 +383,7 @@ "Movies": "Filmovi", "Download": "Preuzmi", "Download as: ": "Preuzmi kao: ", - "%A %B %-d, %Y": "%A, %-d. %B %Y.", + "%A %B %-d, %Y": "%A, %-d. %B %Y", "(edited)": "(uređeno)", "YouTube comment permalink": "Stalna poveznica YouTube komentara", "permalink": "stalna poveznica", @@ -391,7 +391,7 @@ "Audio mode": "Audio modus", "Video mode": "Videomodus", "Videos": "Videa", - "Playlists": "Playliste", + "Playlists": "Zbirke", "Community": "Zajednica", "relevance": "značaj", "rating": "ocjena", @@ -408,7 +408,7 @@ "year": "godina", "video": "video", "channel": "kanal", - "playlist": "playlista", + "playlist": "Zbirka", "movie": "film", "show": "emisija", "hd": "hd", @@ -421,7 +421,7 @@ "hdr": "hdr", "filter": "filtar", "Current version: ": "Trenutačna verzija: ", - "next_steps_error_message": "Nakon toga pokušaj sljedeće: ", + "next_steps_error_message": "Nakon toga bi trebali pokušati sljedeće: ", "next_steps_error_message_refresh": "Aktualiziraj stranicu", "next_steps_error_message_go_to_youtube": "Idi na YouTube", "footer_donate_page": "Doniraj", @@ -433,5 +433,34 @@ "footer_documentation": "Dokumentacija", "footer_original_source_code": "Izvoran izvorni kod", "preferences_region_label": "Zemlja sadržaja: ", - "preferences_quality_dash_label": "Preferirana kvaliteta dash-videa: " + "preferences_quality_dash_label": "Preferirana DASH videokvaliteta: ", + "preferences_quality_option_dash": "DASH (adaptativna kvaliteta)", + "preferences_quality_option_hd720": "HD720", + "preferences_quality_option_medium": "Srednja", + "preferences_quality_dash_option_worst": "Najgora", + "preferences_quality_dash_option_4320p": "4320 p", + "preferences_quality_dash_option_2160p": "2160 p", + "preferences_quality_dash_option_1440p": "1440 p", + "preferences_quality_dash_option_1080p": "1080 p", + "preferences_quality_dash_option_360p": "360 p", + "preferences_quality_dash_option_240p": "240 p", + "preferences_quality_dash_option_144p": "144 p", + "invidious": "Invidious", + "purchased": "Kupljeno", + "360": "360 °", + "none": "bez", + "videoinfo_youTube_embed_link": "Ugradi", + "user_created_playlists": "`x` stvorene zbirke", + "user_saved_playlists": "`x` spremljene zbirke", + "Video unavailable": "Video nedostupan", + "preferences_save_player_pos_label": "Spremi trenutačno vrijeme videa: ", + "videoinfo_watch_on_youTube": "Gledaj na YouTubeu", + "download_subtitles": "Podnaslovi - `x` (.vtt)", + "preferences_quality_dash_option_auto": "Automatska", + "preferences_quality_option_small": "Niska", + "preferences_quality_dash_option_best": "Najbolja", + "preferences_quality_dash_option_720p": "720 p", + "preferences_quality_dash_option_480p": "480 p", + "videoinfo_started_streaming_x_ago": "Započet prijenos prije `x`", + "videoinfo_invidious_embed_link": "Ugradi poveznicu" } diff --git a/locales/hu-HU.json b/locales/hu-HU.json index 2e721a0d..14248e87 100644 --- a/locales/hu-HU.json +++ b/locales/hu-HU.json @@ -1,224 +1,234 @@ { "`x` subscribers": { - "": "`x` feliratkozó" + "": "`x` feliratkozó", + "([^.,0-9]|^)1([^.,0-9]|$)": "`x` feliratkozó" }, "`x` videos": { - "": "`x` videó" + "": "`x` videó", + "([^.,0-9]|^)1([^.,0-9]|$)": "`x` videó" }, "`x` playlists": { - "": "`x` playlist" + "": "`x` lejátszási lista", + "([^.,0-9]|^)1([^.,0-9]|$)": "`x` lejátszási lista" }, "LIVE": "ÉLŐ", - "Shared `x` ago": "`x` óta megosztva", + "Shared `x` ago": "`x` ezelőtt lett megosztva", "Unsubscribe": "Leiratkozás", "Subscribe": "Feliratkozás", - "View channel on YouTube": "csatorna megtekintése a YouTube-on", - "View playlist on YouTube": "lejátszási lista megtekintése a YouTube-on", + "View channel on YouTube": "Csatorna megnézése YouTube-on", + "View playlist on YouTube": "Lejátszási lista megnézése YouTube-on", "newest": "legújabb", "oldest": "legrégibb", "popular": "népszerű", "last": "utolsó", "Next page": "Következő oldal", "Previous page": "Előző oldal", - "Clear watch history?": "Megtekintési napló törlése?", + "Clear watch history?": "Törölve legyen a megnézett videók listája?", "New password": "Új jelszó", - "New passwords must match": "Az új jelszavaknak egyezniük kell", - "Cannot change password for Google accounts": "Google fiók jelszavát nem lehet megváltoztatni", - "Authorize token?": "Token felhatalmazása?", - "Authorize token for `x`?": "Token felhatalmazása `x`-ra?", + "New passwords must match": "Az új jelszavaknak egyezniük kell!", + "Cannot change password for Google accounts": "A Google-fiók jelszavát nem lehet megváltoztatni.", + "Authorize token?": "Engedélyezve legyen a token?", + "Authorize token for `x`?": "Engedélyezve legyen a token erre? „`x`”", "Yes": "Igen", "No": "Nem", "Import and Export Data": "Adatok importálása és exportálása", "Import": "Importálás", - "Import Invidious data": "Invidious adatainak importálása", - "Import YouTube subscriptions": "YouTube feliratkozások importálása", - "Import FreeTube subscriptions (.db)": "FreeTube feliratkozások importálása (.db)", - "Import NewPipe subscriptions (.json)": "NewPipe feliratkozások importálása (.json)", + "Import Invidious data": "Az Invidious adatainak importálása", + "Import YouTube subscriptions": "YouTube-feliratkozások importálása", + "Import FreeTube subscriptions (.db)": "FreeTube-feliratkozások importálása (.db)", + "Import NewPipe subscriptions (.json)": "NewPipe-feliratkozások importálása (.json)", "Import NewPipe data (.zip)": "NewPipe adatainak importálása (.zip)", "Export": "Exportálás", "Export subscriptions as OPML": "Feliratkozások exportálása OPML-ként", - "Export subscriptions as OPML (for NewPipe & FreeTube)": "Feliratkozások exportálása OPML-ként (NewPipe és FreeTube számára)", + "Export subscriptions as OPML (for NewPipe & FreeTube)": "Feliratkozások exportálása OPML-ként (NewPipe-hoz és FreeTube-hoz)", "Export data as JSON": "Adat exportálása JSON-ként", - "Delete account?": "Fiók törlése?", - "History": "Megtekintési napló", + "Delete account?": "Törlésre kerüljön a fiók?", + "History": "Megnézések naplója", "An alternative front-end to YouTube": "Alternatív YouTube front-end", - "JavaScript license information": "JavaScript licensz információ", + "JavaScript license information": "A JavaScript licencinformációja", "source": "forrás", "Log in": "Bejelentkezés", "Log in/register": "Bejelentkezés/Regisztráció", - "Log in with Google": "Bejelentkezés Google fiókkal", - "User ID": "Felhasználó-ID", + "Log in with Google": "Bejelentkezés Google-fiókkal", + "User ID": "Felhasználói azonosító", "Password": "Jelszó", - "Time (h:mm:ss):": "Idő (h:mm:ss):", - "Text CAPTCHA": "Szöveg-CAPTCHA", - "Image CAPTCHA": "Kép-CAPTCHA", + "Time (h:mm:ss):": "A pontos idő (ó:pp:mm):", + "Text CAPTCHA": "Szöveges CAPTCHA kérése", + "Image CAPTCHA": "Kép CAPTCHA kérése", "Sign In": "Bejelentkezés", "Register": "Regisztráció", - "E-mail": "E-mail", - "Google verification code": "Google verifikációs kód", + "E-mail": "E-mail-cím", + "Google verification code": "A Google ellenőrző kódja", "Preferences": "Beállítások", - "preferences_category_player": "Lejátszó beállítások", - "preferences_video_loop_label": "Mindig loop-ol: ", + "preferences_category_player": "Lejátszó beállításai", + "preferences_video_loop_label": "Videó állandó ismétlése: ", "preferences_autoplay_label": "Automatikus lejátszás: ", - "preferences_continue_label": "Következő lejátszása alapértelmezésben: ", - "preferences_continue_autoplay_label": "Következő automatikus lejátszása: ", - "preferences_listen_label": "Hallgatás alapértelmezésben: ", - "preferences_local_label": "Videók proxyzása: ", + "preferences_continue_label": "A következő videót mindig automatikusan játssza le: ", + "preferences_continue_autoplay_label": "A következő videó automatikus lejátszása: ", + "preferences_listen_label": "Mindig csak a hangsáv lejátszása: ", + "preferences_local_label": "Videók proxyn keresztüli lejátszása: ", "preferences_speed_label": "Alapértelmezett sebesség: ", - "preferences_quality_label": "Kívánt video minőség: ", + "preferences_quality_label": "Videó minősége: ", "preferences_volume_label": "Hangerő: ", - "preferences_comments_label": "Alapértelmezett kommentek: ", + "preferences_comments_label": "Mindig innen legyenek betöltve a hozzászólások: ", "youtube": "YouTube", - "reddit": "reddit", - "preferences_captions_label": "Alapértelmezett feliratok: ", + "reddit": "Reddit", + "preferences_captions_label": "Felirat nyelvének sorrendje: ", "Fallback captions: ": "Másodlagos feliratok: ", - "preferences_related_videos_label": "Hasonló videók mutatása: ", - "preferences_annotations_label": "Szövegmagyarázatok mutatása alapértelmezésben: ", - "preferences_extend_desc_label": "Automatikusan növelje meg a videó leírását", - "preferences_vr_mode_label": "Interaktív 360° videók", - "preferences_category_visual": "Kinézeti beállítások", - "preferences_player_style_label": "Lejátszó stílusa: ", - "Dark mode: ": "Sötét mód: ", + "preferences_related_videos_label": "Hasonló videók ajánlása: ", + "preferences_annotations_label": "Szövegmagyarázatok alapértelmezett mutatása: ", + "preferences_extend_desc_label": "A videó leírása automatikusan látható: ", + "preferences_vr_mode_label": "Interaktív, 360°-os videók ", + "preferences_category_visual": "Kinézet, elrendezés és régió beállításai", + "preferences_player_style_label": "Lejátszó kinézete: ", + "Dark mode: ": "Elsötétített mód: ", "preferences_dark_mode_label": "Téma: ", "dark": "sötét", "light": "világos", "preferences_thin_mode_label": "Vékony mód: ", - "preferences_category_subscription": "Feliratkozási beállítások", - "preferences_annotations_subscribed_label": "Szövegmagyarázatok mutatása alapértelmezésben feliratkozott csatornák esetében: ", - "Redirect homepage to feed: ": "Kezdő oldal átirányitása a feed-re: ", - "preferences_max_results_label": "Feed-ben mutatott videók száma: ", - "preferences_sort_label": "Videók sorrendje: ", - "published": "közzétéve", - "published - reverse": "közzétéve - fordítva", - "alphabetically": "ABC sorrend", - "alphabetically - reverse": "ABC sorrend - fordítva", - "channel name": "csatorna neve", - "channel name - reverse": "csatorna neve - fordítva", - "Only show latest video from channel: ": "Csak a legutolsó videó mutatása a csatornából: ", - "Only show latest unwatched video from channel: ": "Csak a legutolsó nem megtekintett videó mutatása a csatornából: ", - "preferences_unseen_only_label": "Csak a nem megtekintettek mutatása: ", - "preferences_notifications_only_label": "Csak értesítések mutatása (ha van): ", - "Enable web notifications": "Web értesítések bekapcsolása", + "preferences_category_subscription": "Feliratkozott tartalmak beállításai", + "preferences_annotations_subscribed_label": "A feliratkozott csatornák szövegmagyarázatainak alapértelmezett mutatása: ", + "Redirect homepage to feed: ": "Kezdőoldal átirányitása a feedre: ", + "preferences_max_results_label": "Feedben mutatott videók száma: ", + "preferences_sort_label": "Videók rendezése: ", + "published": "közzététel szerint", + "published - reverse": "közzététel szerint – fordított sorrendben", + "alphabetically": "ABC-sorrend szerint", + "alphabetically - reverse": "Fordított ABC-sorrend szerint", + "channel name": "csatorna neve szerint", + "channel name - reverse": "csatorna neve szerint – fordított sorrendben", + "Only show latest video from channel: ": "Csak a csatorna legújabb videójának mutatása: ", + "Only show latest unwatched video from channel: ": "Csak a csatorna legújabb, de még nem megnézett videójának mutatása: ", + "preferences_unseen_only_label": "A még nem megnézett videók mutatása: ", + "preferences_notifications_only_label": "Csak az értesítések mutatása (ha van): ", + "Enable web notifications": "Böngészőn belüli értesítések bekapcsolása", "`x` uploaded a video": "`x` feltöltött egy videót", - "`x` is live": "`x` élő", - "preferences_category_data": "Adat beállítások", - "Clear watch history": "Megtekintési napló törlése", - "Import/export data": "Adat Import/Export", - "Change password": "Jelszócsere", + "`x` is live": "`x` élőben közvetít", + "preferences_category_data": "Fiók beállításai és egyéb lehetőségek", + "Clear watch history": "Megnézett videók listájának törlése", + "Import/export data": "Adatok importálása vagy exportálása", + "Change password": "Jelszó megváltoztatása", "Manage subscriptions": "Feliratkozások kezelése", "Manage tokens": "Tokenek kezelése", - "Watch history": "Megtekintési napló", + "Watch history": "Megnézett videók", "Delete account": "Fiók törlése", - "preferences_category_admin": "Adminisztrátor beállítások", - "preferences_default_home_label": "Alapértelmezett oldal: ", - "preferences_feed_menu_label": "Feed menü: ", - "Top enabled: ": "Top lista engedélyezve: ", + "preferences_category_admin": "Adminisztrátorok beállításai", + "preferences_default_home_label": "Kezdőoldal: ", + "preferences_feed_menu_label": "Feed menü sorrendje: ", + "Top enabled: ": "Toplista engedélyezve: ", "CAPTCHA enabled: ": "CAPTCHA engedélyezve: ", "Login enabled: ": "Bejelentkezés engedélyezve: ", - "Registration enabled: ": "Registztráció engedélyezve: ", - "Report statistics: ": "Statisztikák gyűjtése: ", + "Registration enabled: ": "Regisztráció engedélyezve: ", + "Report statistics: ": "Statisztika jelentése: ", "Save preferences": "Beállítások mentése", - "Subscription manager": "Feliratkozás kezelő", - "Token manager": "Token kezelő", + "Subscription manager": "Feliratkozások kezelője", + "Token manager": "Tokenek kezelője", "Token": "Token", "`x` subscriptions": { - "": "`x` feliratkozás" + "": "`x` csatornára van feliratkozás", + "([^.,0-9]|^)1([^.,0-9]|$)": "`x` csatornára van feliratkozás" }, "`x` tokens": { - "": "`x` token" + "": "`x` token", + "([^.,0-9]|^)1([^.,0-9]|$)": "`x` token" }, - "Import/export": "Import/export", + "Import/export": "Importálás/exportálás", "unsubscribe": "leiratkozás", "revoke": "visszavonás", "Subscriptions": "Feliratkozások", "`x` unseen notifications": { - "": "`x` kimaradt érdesítés" + "": "`x` kimaradt értesítés", + "([^.,0-9]|^)1([^.,0-9]|$)": "`x` kimaradt értesítés" }, - "search": "keresés", + "search": "Videó keresése", "Log out": "Kijelentkezés", - "Source available here.": "A forráskód itt érhető el.", - "View JavaScript license information.": "JavaScript licensz inforkációk megtekintése.", - "View privacy policy.": "Adatvédelmi irányelvek megtekintése.", + "Source available here.": "A forráskód itt érhető el", + "View JavaScript license information.": "JavaScript licencinformáció megnyitása", + "View privacy policy.": "Adatvédelmi szabályzat megnyitása", "Trending": "Felkapott", - "Public": "Nyilvános", - "Unlisted": "Nem nyilvános", - "Private": "Privát", - "View all playlists": "Minden lejátszási lista megtekintése", - "Updated `x` ago": "Frissitve: `x`", - "Delete playlist `x`?": "`x` playlist törlése?", + "Public": "nyilvános", + "Unlisted": "nem nyilvános", + "Private": "magán", + "View all playlists": "Összes lejátszási lista megnézése", + "Updated `x` ago": "`x` ezelőtt lett frissítve", + "Delete playlist `x`?": "Törlésre kerüljön ez a lejátszási lista? „`x`”", "Delete playlist": "Lejátszási lista törlése", "Create playlist": "Lejátszási lista létrehozása", - "Title": "Cím", + "Title": "Lejátszási lista címe", "Playlist privacy": "Lejátszási lista láthatósága", - "Editing playlist `x`": "`x` lista szerkesztése", - "Show more": "Mutass többet", - "Show less": "Mutass kevesebbet", - "Watch on YouTube": "Megtekintés a YouTube-on", - "Hide annotations": "Szövegmagyarázat elrejtése", - "Show annotations": "Szövegmagyarázat mutatása", + "Editing playlist `x`": "„`x`” lejátszási lista szerkesztése", + "Show more": "Többi szöveg mutatása", + "Show less": "Kevesebb szöveg mutatása", + "Watch on YouTube": "Megnézés a YouTube-on", + "Hide annotations": "Megjegyzések elrejtése", + "Show annotations": "Megjegyzések mutatása", "Genre: ": "Műfaj: ", - "License: ": "Licensz: ", + "License: ": "Licenc: ", "Family friendly? ": "Családbarát? ", "Wilson score: ": "Wilson-pontszám: ", - "Engagement: ": "elkötelezettség: ", + "Engagement: ": "Visszajelzési mutató: ", "Whitelisted regions: ": "Engedélyezett régiók: ", "Blacklisted regions: ": "Tiltott régiók: ", - "Shared `x`": "Megosztva `x`", + "Shared `x`": "`x` osztották meg", "`x` views": { - "": "`x` megtekintés" + "": "`x` alkalommal nézték meg", + "([^.,0-9]|^)1([^.,0-9]|$)": "`x` alkalommal nézték meg" }, - "Premieres in `x`": "premierel `x` múlva", - "Premieres `x`": "`x`-t premierel", - "Hi! Looks like you have JavaScript turned off. Click here to view comments, keep in mind they may take a bit longer to load.": "Úgy látszik, hogy a JavaScript ki van kapcsolva a böngésződben. Kattints ide hogy megtekintsd a kommenteket, de tudd, hogy így kicsit tovább tarthat a betöltés.", - "View YouTube comments": "YouTube kommentek megtekintése", - "View more comments on Reddit": "További kommentek megtekintése Redditen", + "Premieres in `x`": "`x` később lesz a premierje", + "Premieres `x`": "`x` lesz a premierje", + "Hi! Looks like you have JavaScript turned off. Click here to view comments, keep in mind they may take a bit longer to load.": "Helló! Úgy tűnik a JavaScript ki van kapcsolva a böngészőben. Ide kattintva lehet olvasni a hozzászólásokat, de a betöltésük így kicsit több időbe fog telni.", + "View YouTube comments": "YouTube-on lévő hozzászólások olvasása", + "View more comments on Reddit": "A többi hozzászólás olvasása Redditen", "View `x` comments": { - "": "`x` komment megtekintése" + "": "`x` hozzászólás olvasása", + "([^.,0-9]|^)1([^.,0-9]|$)": "`x` hozzászólás olvasása" }, - "View Reddit comments": "Reddit kommentek megtekintése", + "View Reddit comments": "Redditen lévő hozzászólások olvasása", "Hide replies": "Válaszok elrejtése", "Show replies": "Válaszok mutatása", - "Incorrect password": "Helytelen jelszó", - "Quota exceeded, try again in a few hours": "Kvóta túllépve, próbálkozz pár órával később", - "Unable to log in, make sure two-factor authentication (Authenticator or SMS) is turned on.": "Sikertelen bejelentkezés. Győződj meg róla, hogy a kétfaktoros hitelesítés (hitelesítő vagy SMS) engedélyezve van.", - "Login failed. This may be because two-factor authentication is not turned on for your account.": "Sikertelen bejelentkezés. Győződj meg róla, hogy a kétfaktoros hitelesítés engedélyezve van.", - "Wrong answer": "Rossz válasz", - "Erroneous CAPTCHA": "Hibás CAPTCHA", - "CAPTCHA is a required field": "A CAPTCHA kötelező", - "User ID is a required field": "A felhasználó-ID kötelező", - "Password is a required field": "A jelszó kötelező", - "Wrong username or password": "Rossz felhasználónév vagy jelszó", - "Please sign in using 'Log in with Google'": "Kérem, jelentkezzen be a \"Bejelentkezés Google-el\"", - "Password cannot be empty": "A jelszó nem lehet üres", - "Password cannot be longer than 55 characters": "A jelszó nem lehet hosszabb 55 karakternél", - "Please log in": "Kérem lépjen be", - "Invidious Private Feed for `x`": "`x` Invidious privát feed-je", - "channel:`x`": "`x` csatorna", - "Deleted or invalid channel": "Törölt vagy nemlétező csatorna", - "This channel does not exist.": "Ez a csatorna nem létezik.", - "Could not get channel info.": "Nem sikerült lekérni a csatorna adatokat.", - "Could not fetch comments": "Nem sikerült lekérni a kommenteket", + "Incorrect password": "A jelszó nem megfelelő", + "Quota exceeded, try again in a few hours": "A kvótát meghaladták. Néhány órával később próbáld meg újból betölteni.", + "Unable to log in, make sure two-factor authentication (Authenticator or SMS) is turned on.": "Nem sikerült bejelentkezni. A kétlépcsős (hitelesítő vagy szöveges üzenet általi) hitelesítésnek bekapcsolva kell lennie.", + "Login failed. This may be because two-factor authentication is not turned on for your account.": "Nem sikerült bejelentkezni. Ennek oka lehet, hogy a kétlépcsős hitelesítés nincs bekapcsolva a fiók beállításaiban.", + "Wrong answer": "Nem jól válaszoltál.", + "Erroneous CAPTCHA": "A CAPTCHA hibás.", + "CAPTCHA is a required field": "A CAPTCHA-mezőt ki kell tölteni.", + "User ID is a required field": "A felhasználói azonosítót meg kell adni!", + "Password is a required field": "Meg kell adni egy jelszót.", + "Wrong username or password": "Vagy a felhasználói név, vagy pedig a jelszó nem megfelelő.", + "Please sign in using 'Log in with Google'": "A „Bejelentkezés Google-el” gombbal jelentkezz be!", + "Password cannot be empty": "A jelszót nem lehet kihagyni.", + "Password cannot be longer than 55 characters": "A jelszó nem lehet hosszabb 55 karakternél.", + "Please log in": "Kérjük, jelentkezz be!", + "Invidious Private Feed for `x`": "„`x`” Invidious magán feedje", + "channel:`x`": "`x` csatornája", + "Deleted or invalid channel": "A csatorna érvénytelen, vagy pedig törölve lett.", + "This channel does not exist.": "Nincs ilyen csatorna.", + "Could not get channel info.": "Nem lehetett betölteni a csatorna adatait.", + "Could not fetch comments": "Nem lehetett betölteni a hozzászólásokat.", "View `x` replies": { - "": "`x` válasz megtekintése" + "": "`x` válasz olvasása", + "([^.,0-9]|^)1([^.,0-9]|$)": "`x` válasz olvasása" }, - "`x` ago": "`x` óta", - "Load more": "További betöltése", + "`x` ago": "`x` ezelőtt", + "Load more": "Többi hozzászólás betöltése", "`x` points": { - "": "`x` pont" + "": "`x` pont", + "([^.,0-9]|^)1([^.,0-9]|$)": "`x` pont" }, - "Could not create mix.": "Nem tudok mix-et készíteni.", + "Could not create mix.": "A válogatást nem lehetett elkészíteni.", "Empty playlist": "Üres lejátszási lista", - "Not a playlist.": "Nem lejátszási lista.", + "Not a playlist.": "Ez nem egy lejátszási lista.", "Playlist does not exist.": "Nincs ilyen lejátszási lista.", - "Could not pull trending pages.": "Nem sikerült lekérni a felkapott oldalt.", - "Hidden field \"challenge\" is a required field": "A rejtett \"challenge\" mező kötelező", - "Hidden field \"token\" is a required field": "A rejtett \"token\" mező kötelező", + "Could not pull trending pages.": "Nem lehetett betölteni a felkapott videók oldalát.", + "Hidden field \"challenge\" is a required field": "A rejtett „challenge” mezőt ki kell tölteni.", + "Hidden field \"token\" is a required field": "A rejtett „token” mezőt ki kell tölteni.", "Erroneous challenge": "Hibás challenge", "Erroneous token": "Hibás token", "No such user": "Nincs ilyen felhasználó", - "Token is expired, please try again": "Lejárt token, kérem próbáld újra", + "Token is expired, please try again": "A token lejárt. Kérjük, próbáld meg újból!", "English": "angol", - "English (auto-generated)": "angol (automatikusan generált)", + "English (auto-generated)": "angol (automatikusan létrehozott)", "Afrikaans": "afrikaans", "Albanian": "albán", "Amharic": "amhara", @@ -245,10 +255,10 @@ "Filipino": "filippínó", "Finnish": "finn", "French": "francia", - "Galician": "galíciai", + "Galician": "galiciai", "Georgian": "grúz", "German": "német", - "Greek": "görök", + "Greek": "görög", "Gujarati": "gudzsaráti", "Haitian Creole": "haiti kreol", "Hausa": "hausza", @@ -259,13 +269,13 @@ "Hungarian": "magyar", "Icelandic": "izlandi", "Igbo": "igbo", - "Indonesian": "indonéziai", + "Indonesian": "indonéz", "Irish": "ír", "Italian": "olasz", "Japanese": "japán", "Javanese": "jávai", "Kannada": "kannada", - "Kazakh": "kazah", + "Kazakh": "kazak", "Khmer": "khmer", "Korean": "koreai", "Kurdish": "kurd", @@ -275,17 +285,17 @@ "Latvian": "lett", "Lithuanian": "litván", "Luxembourgish": "luxemburgi", - "Macedonian": "macedóniai", + "Macedonian": "macedón", "Malagasy": "madagaszkári", "Malay": "maláj", "Malayalam": "malajálam", "Maltese": "máltai", "Maori": "maori", - "Marathi": "Maráthi", + "Marathi": "maráthi", "Mongolian": "mongol", "Nepali": "nepáli", - "Norwegian Bokmål": "bokmål", - "Nyanja": "nyánja", + "Norwegian Bokmål": "norvég (bokmål)", + "Nyanja": "njándzsa (csicseva)", "Pashto": "pastu", "Persian": "perzsa", "Polish": "lengyel", @@ -296,19 +306,19 @@ "Samoan": "szamoai", "Scottish Gaelic": "skót gael", "Serbian": "szerb", - "Shona": "shona", - "Sindhi": "szindhi", + "Shona": "sona", + "Sindhi": "szindi", "Sinhala": "szingaléz", "Slovak": "szlovák", "Slovenian": "szlovén", "Somali": "szomáliai", - "Southern Sotho": "déli szothó", + "Southern Sotho": "déli szútú", "Spanish": "spanyol", - "Spanish (Latin America)": "spanyol (Latin-Amerika)", + "Spanish (Latin America)": "spanyol (latinamerikai)", "Sundanese": "szunda", "Swahili": "szuahéli", - "Swedish": "svld", - "Tajik": "tadzsik", + "Swedish": "svéd", + "Tajik": "tádzsik", "Tamil": "tamil", "Telugu": "telugu", "Thai": "thai", @@ -322,49 +332,135 @@ "Yoruba": "joruba", "Zulu": "zulu", "`x` years": { - "": "`x` év" + "": "`x` évvel", + "([^.,0-9]|^)1([^.,0-9]|$)": "`x` évvel" }, "`x` months": { - "": "`x` hónap" + "": "x` hónappal", + "([^.,0-9]|^)1([^.,0-9]|$)": "`x` hónappal" }, "`x` weeks": { - "": "`x` hét" + "": "`x` héttel", + "([^.,0-9]|^)1([^.,0-9]|$)": "`x` héttel" }, "`x` days": { - "": "`x` nap" + "": "`x` nappal", + "([^.,0-9]|^)1([^.,0-9]|$)": "`x` nappal" }, "`x` hours": { - "": "`x` óra" + "": "`x` órával", + "([^.,0-9]|^)1([^.,0-9]|$)": "`x` órával" }, "`x` minutes": { - "": "`x` perc" + "": "`x` perccel", + "([^.,0-9]|^)1([^.,0-9]|$)": "`x` perccel" }, "`x` seconds": { - "": "`x` másodperc" + "": "`x` másodperccel", + "([^.,0-9]|^)1([^.,0-9]|$)": "`x` másodperccel" }, "Fallback comments: ": "Másodlagos kommentek: ", "Popular": "Népszerű", - "Search": "Keresés", + "Search": "Keresési oldal", "Top": "Top", "About": "Leírás", "Rating: ": "Besorolás: ", "preferences_locale_label": "Nyelv: ", - "View as playlist": "Megtekintés lejátszási listaként", + "View as playlist": "Megnézés lejátszási listában", "Default": "Alapértelmezett", - "Music": "Zene", + "Music": "Zenék", "Gaming": "Játékok", "News": "Hírek", "Movies": "Filmek", "Download": "Letöltés", - "Download as: ": "Letöltés mint: ", + "Download as: ": "Letöltés másként: ", "(edited)": "(szerkesztve)", - "YouTube comment permalink": "YouTube komment permalink", + "YouTube comment permalink": "YouTube-hozzászólás permalinkje", "permalink": "permalink", - "`x` marked it with a ❤": "`x` jelölte ❤-vel", - "Audio mode": "Audió mód", - "Video mode": "Hang mód", + "`x` marked it with a ❤": "`x` egy ❤ jellel jelölte meg", + "Audio mode": "Csak hanggal", + "Video mode": "Hanggal és képpel", "Videos": "Videók", "Playlists": "Lejátszási listák", "Community": "Közösség", - "Current version: ": "Jelenlegi verzió: " + "Current version: ": "Jelenlegi verzió: ", + "preferences_quality_option_medium": "Közepes", + "preferences_quality_dash_option_auto": "Automatikus", + "preferences_quality_dash_option_best": "Legjobb", + "preferences_quality_dash_option_worst": "Legrosszabb", + "preferences_quality_dash_option_4320p": "4320p", + "preferences_quality_dash_option_1080p": "1080p", + "preferences_quality_dash_option_720p": "720p", + "preferences_quality_dash_option_480p": "480p", + "preferences_quality_dash_option_360p": "360p", + "preferences_quality_dash_option_240p": "240p", + "preferences_quality_dash_option_144p": "144p", + "invidious": "Invidious", + "videoinfo_started_streaming_x_ago": "`x` ezelőtt kezdte streamelni", + "views": "Megnézések száma szerint", + "purchased": "Megvásárolva", + "360": "360°-os", + "footer_original_source_code": "Eredeti forráskód", + "none": "egyik sem", + "videoinfo_watch_on_youTube": "Megnézés a YouTube-on", + "videoinfo_youTube_embed_link": "Beágyazás", + "videoinfo_invidious_embed_link": "Beágyazás linkje", + "download_subtitles": "Felirat – `x` (.vtt)", + "user_created_playlists": "`x` létrehozott lejátszási lista", + "user_saved_playlists": "`x` mentett lejátszási lista", + "preferences_quality_option_hd720": "HD720", + "preferences_quality_option_dash": "DASH (adaptív minőség)", + "preferences_quality_dash_option_2160p": "2160p", + "preferences_quality_dash_option_1440p": "1440p", + "preferences_quality_dash_label": "DASH-videó minősége: ", + "preferences_quality_option_small": "Rossz", + "date": "Feltöltés dátuma szerint", + "Video unavailable": "A videó nem érhető el", + "preferences_save_player_pos_label": "A videó folytatása onnan, ahol félbe lett hagyva: ", + "preferences_show_nick_label": "Becenév mutatása felül: ", + "Released under the AGPLv3 on Github.": "Az AGPLv3 licenc alapján, a GitHubon elérhető", + "3d": "3D-ben", + "live": "Élőben", + "filter": "Szűrők", + "next_steps_error_message_refresh": "Újratöltés", + "footer_donate_page": "Adakozás", + "footer_source_code": "Forráskód", + "footer_modfied_source_code": "Módosított forráskód", + "adminprefs_modified_source_code_url_label": "A módosított forráskód repositoryjának URL-je:", + "preferences_automatic_instance_redirect_label": "Váltáskor másik Invidious oldal automatikus betöltése (redirect.invidious.io töltődik, ha nem működne): ", + "preferences_region_label": "Ország tartalmainak mutatása: ", + "relevance": "Relevancia szerint", + "rating": "Besorolás szerint", + "content_type": "Típus", + "today": "Mai napon", + "channel": "Csatorna", + "video": "Videó", + "playlist": "Lejátszási lista", + "creative_commons": "Creative Commons", + "features": "Jellemzők", + "sort": "Rendezés módja", + "preferences_category_misc": "További beállítások", + "%A %B %-d, %Y": "%Y. %B %-d %A", + "long": "Hosszú (több, mint 20 perces)", + "year": "Ebben az évben", + "hour": "Az elmúlt órában", + "movie": "Film", + "hdr": "HDR", + "Broken? Try another Invidious Instance": "Nem működik? Próbáld meg egy másik Invidious oldallal!", + "duration": "Játékidő", + "next_steps_error_message": "Az alábbi lehetőségek állnak rendelkezésre: ", + "Xhosa": "xhosza", + "Switch Invidious Instance": "Váltás másik Invidious-oldalra", + "Urdu": "urdu", + "week": "Ezen a héten", + "Invalid TFA code": "A kétlépéses hitelesítés kódja nem megfelelő", + "footer_documentation": "Dokumentáció", + "hd": "HD", + "next_steps_error_message_go_to_youtube": "Ugrás a YouTube-ra", + "show": "Műsor", + "4k": "4K", + "short": "Rövid (kevesebb, mint 4 perces)", + "month": "Ebben a hónapban", + "subtitles": "Felirattal", + "location": "Közelben" } diff --git a/locales/id.json b/locales/id.json index 054a2557..b3918955 100644 --- a/locales/id.json +++ b/locales/id.json @@ -433,5 +433,34 @@ "footer_modfied_source_code": "Kode sumber yang dimodifikasi", "footer_documentation": "Dokumentasi", "preferences_region_label": "Konten dari negara: ", - "preferences_quality_dash_label": "Kualitas video dash yang disukai: " + "preferences_quality_dash_label": "Kualitas video DASH yang disukai: ", + "preferences_quality_option_medium": "Medium", + "preferences_quality_option_small": "Rendah", + "preferences_quality_dash_option_best": "Terbaik", + "preferences_quality_dash_option_worst": "Terburuk", + "preferences_quality_dash_option_4320p": "4320p", + "preferences_quality_dash_option_2160p": "2160p", + "preferences_quality_dash_option_1080p": "1080p", + "preferences_quality_dash_option_720p": "720p", + "preferences_quality_dash_option_360p": "360p", + "preferences_quality_dash_option_240p": "240p", + "preferences_quality_dash_option_144p": "144p", + "invidious": "Invidious", + "purchased": "Dibeli", + "360": "360°", + "none": "tidak ada", + "videoinfo_watch_on_youTube": "Tonton di YouTube", + "videoinfo_youTube_embed_link": "Tersemat", + "videoinfo_invidious_embed_link": "Tautan Tersemat", + "download_subtitles": "Takarir- `x` (.vtt)", + "user_saved_playlists": "`x` daftar putar yang disimpan", + "videoinfo_started_streaming_x_ago": "Mulai siaran `x` yang lalu", + "user_created_playlists": "`x` daftar putar yang dibuat", + "preferences_quality_option_dash": "DASH (kualitas adaptif)", + "preferences_quality_option_hd720": "HD720", + "preferences_quality_dash_option_1440p": "1440p", + "preferences_quality_dash_option_auto": "Otomatis", + "preferences_quality_dash_option_480p": "480p", + "Video unavailable": "Video tidak tersedia", + "preferences_save_player_pos_label": "Simpan waktu video saat ini: " } diff --git a/locales/ja.json b/locales/ja.json index c7764d33..bf858f1f 100644 --- a/locales/ja.json +++ b/locales/ja.json @@ -433,5 +433,21 @@ "long": "20 分以上", "preferences_region_label": "地域: ", "footer_donate_page": "寄付する", - "preferences_quality_dash_label": "優先するDash画質 : " + "preferences_quality_dash_label": "優先するDash画質 : ", + "preferences_quality_dash_option_4320p": "4320p", + "preferences_quality_dash_option_240p": "240p", + "preferences_quality_dash_option_144p": "144p", + "preferences_quality_option_hd720": "HD720", + "preferences_quality_option_medium": "中", + "preferences_quality_option_small": "小", + "invidious": "Invidious", + "preferences_quality_dash_option_auto": "自動", + "preferences_quality_dash_option_720p": "720p", + "preferences_quality_dash_option_360p": "360p", + "preferences_quality_dash_option_2160p": "2160p", + "preferences_quality_dash_option_1080p": "1080p", + "preferences_quality_dash_option_1440p": "1440p", + "preferences_quality_dash_option_480p": "480p", + "videoinfo_youTube_embed_link": "埋め込み", + "videoinfo_invidious_embed_link": "埋め込みリンク" } diff --git a/locales/nb-NO.json b/locales/nb-NO.json index 3dd76c1f..14361224 100644 --- a/locales/nb-NO.json +++ b/locales/nb-NO.json @@ -431,5 +431,35 @@ "footer_source_code": "Kildekode", "footer_original_source_code": "Opprinnelig kildekode", "footer_modfied_source_code": "Endret kildekode", - "adminprefs_modified_source_code_url_label": "Nettadresse til kodelager inneholdende endret kildekode" + "adminprefs_modified_source_code_url_label": "Nettadresse til kodelager inneholdende endret kildekode", + "preferences_quality_dash_label": "Foretrukket DASH-videokvalitet: ", + "preferences_region_label": "Innholdsland: ", + "preferences_quality_option_hd720": "HD720", + "preferences_quality_option_small": "Lav", + "preferences_quality_dash_option_auto": "Auto", + "preferences_quality_dash_option_best": "Best", + "preferences_quality_dash_option_worst": "Verst", + "preferences_quality_dash_option_4320p": "4320p", + "preferences_quality_dash_option_1440p": "1440p", + "preferences_quality_dash_option_1080p": "1080p", + "preferences_quality_dash_option_720p": "720p", + "preferences_quality_dash_option_480p": "480p", + "preferences_quality_dash_option_360p": "360p", + "preferences_quality_dash_option_240p": "240p", + "preferences_quality_dash_option_144p": "144p", + "invidious": "Invidious", + "purchased": "Kjøpt", + "360": "360°", + "none": "intet", + "videoinfo_watch_on_youTube": "Se på YouTube", + "videoinfo_youTube_embed_link": "Bak inn", + "videoinfo_invidious_embed_link": "Bak inn lenke", + "download_subtitles": "Undertekster - `x` (.vtt)", + "user_created_playlists": "`x` spillelister opprettet", + "user_saved_playlists": "`x` spillelister lagret", + "Video unavailable": "Utilgjengelig video", + "preferences_quality_option_dash": "DASH (tilpasset kvalitet)", + "preferences_quality_option_medium": "Medium", + "preferences_quality_dash_option_2160p": "2160p", + "videoinfo_started_streaming_x_ago": "Strømmen startet for `x` siden" } diff --git a/locales/nl.json b/locales/nl.json index e523246b..c51d6e18 100644 --- a/locales/nl.json +++ b/locales/nl.json @@ -42,7 +42,7 @@ "Export subscriptions as OPML": "Abonnementen exporteren als OPML", "Export subscriptions as OPML (for NewPipe & FreeTube)": "Abonnementen exporteren als OPML (voor NewPipe en FreeTube)", "Export data as JSON": "Gegevens exporteren als JSON", - "Delete account?": "Wil je je account verwijderen?", + "Delete account?": "Wilt u uw account verwijderen?", "History": "Geschiedenis", "An alternative front-end to YouTube": "Een alternatief front-end voor YouTube", "JavaScript license information": "JavaScript-licentieinformatie", @@ -414,5 +414,53 @@ "location": "locatie", "hdr": "HDR", "filter": "verfijnen", - "Current version: ": "Huidige versie: " + "Current version: ": "Huidige versie: ", + "Switch Invidious Instance": "Schakel tussen de Invidious Instanties", + "preferences_automatic_instance_redirect_label": "Automatische instantie-omleiding (terugval naar redirect.invidious.io): ", + "preferences_quality_dash_label": "Gewenste DASH-videokwaliteit: ", + "preferences_region_label": "Inhoud land: ", + "preferences_category_misc": "Diverse voorkeuren", + "preferences_show_nick_label": "Toon bijnaam bovenaan: ", + "Released under the AGPLv3 on Github.": "Uitgebracht onder de AGPLv3 op Github.", + "short": "Kort (<4 minuten)", + "next_steps_error_message_refresh": "Vernieuwen", + "next_steps_error_message_go_to_youtube": "Ga naar YouTube", + "footer_donate_page": "Doneren", + "footer_documentation": "Documentatie", + "footer_original_source_code": "Originele bron-code", + "footer_modfied_source_code": "Gewijzigde bron-code", + "adminprefs_modified_source_code_url_label": "URL naar gewijzigde bron-code-opslagplaats", + "Broken? Try another Invidious Instance": "Kapot? Probeer een andere Invidious Instantie", + "next_steps_error_message": "Waarna u moet proberen om: ", + "footer_source_code": "Bron-code", + "long": "Lang (> 20 minuten)", + "preferences_quality_option_dash": "DASH (adaptieve kwaliteit)", + "preferences_quality_option_hd720": "HD720", + "preferences_quality_option_medium": "Gemiddeld", + "preferences_quality_option_small": "Klein", + "preferences_quality_dash_option_auto": "Automatisch", + "preferences_quality_dash_option_best": "Beste", + "preferences_quality_dash_option_worst": "Slechtste", + "preferences_quality_dash_option_4320p": "4320p", + "preferences_quality_dash_option_2160p": "2160p", + "preferences_quality_dash_option_1440p": "1440p", + "preferences_quality_dash_option_1080p": "1080p", + "preferences_quality_dash_option_720p": "720p", + "preferences_quality_dash_option_480p": "480p", + "preferences_quality_dash_option_360p": "360p", + "preferences_quality_dash_option_240p": "240p", + "preferences_quality_dash_option_144p": "144p", + "invidious": "Invidious", + "videoinfo_started_streaming_x_ago": "Stream `x` geleden begonnen", + "videoinfo_watch_on_youTube": "Bekijken op YouTube", + "videoinfo_youTube_embed_link": "Inbedden", + "videoinfo_invidious_embed_link": "Link ingebedde versie", + "download_subtitles": "Ondertiteling - `x` (.vtt)", + "user_created_playlists": "`x` afspeellijsten aangemaakt", + "user_saved_playlists": "`x` afspeellijsten opgeslagen", + "Video unavailable": "Video onbeschikbaar", + "preferences_save_player_pos_label": "Huidig afspeeltijdstip opslaan: ", + "none": "geen", + "purchased": "Gekocht", + "360": "360º" } diff --git a/locales/sq.json b/locales/sq.json new file mode 100644 index 00000000..0967ef42 --- /dev/null +++ b/locales/sq.json @@ -0,0 +1 @@ +{} diff --git a/locales/sr.json b/locales/sr.json index e0713b43..03b6cf9e 100644 --- a/locales/sr.json +++ b/locales/sr.json @@ -1,437 +1,437 @@ { "`x` subscribers": { - "([^.,0-9]|^)1([^.,0-9]|$)": "`x` пратилаца", - "": "`x` пратилаца" + "([^.,0-9]|^)1([^.,0-9]|$)": "`x` pratilac", + "": "`x` pratilaca" }, "`x` videos": { - "([^.,0-9]|^)1([^.,0-9]|$)": "`x` видео записа", - "": "`x` видео записа" + "([^.,0-9]|^)1([^.,0-9]|$)": "`x` video zapis", + "": "`x` video zapisa" }, "`x` playlists": { - "([^.,0-9]|^)1([^.,0-9]|$)": "`x` списака извођења", - "": "`x` списака извођења" + "([^.,0-9]|^)1([^.,0-9]|$)": "`x` spisak izvođenja", + "": "`x` spisaka izvođenja" }, - "LIVE": "УЖИВО", - "Shared `x` ago": "Подељено пре `x`", - "Unsubscribe": "Прекини праћење", - "Subscribe": "Прати", - "View channel on YouTube": "Погледај канал на YouTube-у", - "View playlist on YouTube": "Погледај списак извођења на YouTube-у", - "newest": "најновије", - "oldest": "најстарије", - "popular": "популарно", - "last": "последње", - "Next page": "Следећа страница", - "Previous page": "Претходна страница", - "Clear watch history?": "Избрисати повест прегледања?", - "New password": "Нова запорка", - "New passwords must match": "Нове запорке морају бити истоветне", - "Cannot change password for Google accounts": "Није могуће променити запорку за Google налоге", - "Authorize token?": "Овласти токен?", - "Authorize token for `x`?": "Овласти токен за `x`?", - "Yes": "Да", - "No": "Не", - "Import and Export Data": "Увоз и извоз података", - "Import": "Увези", - "Import Invidious data": "Увези податке са Invidious-а", - "Import YouTube subscriptions": "Увези праћења са YouTube-а", - "Import FreeTube subscriptions (.db)": "Увези праћења са FreeTube-а (.db)", - "Import NewPipe subscriptions (.json)": "Увези праћења са NewPipe-а (.json)", - "Import NewPipe data (.zip)": "Увези податке са NewPipe-а (.zip)", - "Export": "Извези", - "Export subscriptions as OPML": "Извези праћења као OPML датотеку", - "Export subscriptions as OPML (for NewPipe & FreeTube)": "Извези праћења као OPML датотеку (за NewPipe и FreeTube)", - "Export data as JSON": "Извези податке као JSON датотеку", - "Delete account?": "Избрисати рачун?", - "History": "Повест", - "An alternative front-end to YouTube": "Заменски кориснички слој за YouTube", - "JavaScript license information": "Извештај о JavaScript одобрењу", - "source": "извор", - "Log in": "Пријави се", - "Log in/register": "Пријави се/Отвори налог", - "Log in with Google": "Пријави се помоћу Google-а", - "User ID": "Кориснички ИД", - "Password": "Запорка", - "Time (h:mm:ss):": "Време (ч:мм:сс):", - "Text CAPTCHA": "Знаковни CAPTCHA", - "Image CAPTCHA": "Сликовни CAPTCHA", - "Sign In": "Пријава", - "Register": "Отвори налог", - "E-mail": "Е-пошта", - "Google verification code": "Google-ов оверни кôд", - "Preferences": "Подешавања", - "preferences_category_player": "Подешавања репродуктора", - "preferences_video_loop_label": "Увек понављај: ", - "preferences_autoplay_label": "Самопуштање: ", - "preferences_continue_label": "Увек подразумевано пуштај следеће: ", - "preferences_continue_autoplay_label": "Самопуштање следећег видео записа: ", - "preferences_listen_label": "Увек подразумевано укључен само звук: ", - "preferences_local_label": "Приказ видео записа преко посредника: ", - "Playlist privacy": "Подешавања приватности плеј листе", - "Editing playlist `x`": "Измена плеј листе `x`", - "Please sign in using 'Log in with Google'": "Молимо Вас да се пријавите помоћу 'Log in with Google'", - "Playlist does not exist.": "Непостојећа плеј листа.", - "Erroneous challenge": "Погрешан изазов", - "Maltese": "Малтешки", - "Download": "Преузми", - "Download as: ": "Преузми као: ", - "Quota exceeded, try again in a few hours": "Квота је премашена, молимо Вас да покушате за пар сати", - "Bangla": "Бангла/Бенгалски", - "preferences_quality_dash_label": "Преферирани квалитет dash видео формата: ", - "Token manager": "Меначер токена", - "Token": "Токен", + "LIVE": "UŽIVO", + "Shared `x` ago": "Podeljeno pre `x`", + "Unsubscribe": "Prekini praćenje", + "Subscribe": "Prati", + "View channel on YouTube": "Pogledaj kanal na YouTube-u", + "View playlist on YouTube": "Pogledaj spisak izvođenja na YouTube-u", + "newest": "najnovije", + "oldest": "najstarije", + "popular": "popularno", + "last": "poslednje", + "Next page": "Sledeća stranica", + "Previous page": "Prethodna stranica", + "Clear watch history?": "Izbrisati povest pregledanja?", + "New password": "Nova lozinka", + "New passwords must match": "Nove lozinke moraju biti istovetne", + "Cannot change password for Google accounts": "Nije moguće promeniti lozinku za Google naloge", + "Authorize token?": "Ovlasti žeton?", + "Authorize token for `x`?": "Ovlasti žeton za `x`?", + "Yes": "Da", + "No": "Ne", + "Import and Export Data": "Uvoz i Izvoz Podataka", + "Import": "Uvezi", + "Import Invidious data": "Uvezi podatke sa Invidious-a", + "Import YouTube subscriptions": "Uvezi praćenja sa YouTube-a", + "Import FreeTube subscriptions (.db)": "Uvezi praćenja sa FreeTube-a (.db)", + "Import NewPipe subscriptions (.json)": "Uvezi praćenja sa NewPipe-a (.json)", + "Import NewPipe data (.zip)": "Uvezi podatke sa NewPipe-a (.zip)", + "Export": "Izvezi", + "Export subscriptions as OPML": "Izvezi praćenja kao OPML datoteku", + "Export subscriptions as OPML (for NewPipe & FreeTube)": "Izvezi praćenja kao OPML datoteku (za NewPipe i FreeTube)", + "Export data as JSON": "Izvezi podatke kao JSON datoteku", + "Delete account?": "Izbrišite nalog?", + "History": "Istorija", + "An alternative front-end to YouTube": "Zamenski korisnički sloj za YouTube", + "JavaScript license information": "Izveštaj o JavaScript odobrenju", + "source": "izvor", + "Log in": "Prijavi se", + "Log in/register": "Prijavi se/Otvori nalog", + "Log in with Google": "Prijavi se pomoću Google-a", + "User ID": "Korisnički ID", + "Password": "Lozinka", + "Time (h:mm:ss):": "Vreme (č:mm:ss):", + "Text CAPTCHA": "Znakovni CAPTCHA", + "Image CAPTCHA": "Slikovni CAPTCHA", + "Sign In": "Prijava", + "Register": "Otvori nalog", + "E-mail": "E-pošta", + "Google verification code": "Google-ova overna koda", + "Preferences": "Podešavanja", + "preferences_category_player": "Podešavanja reproduktora", + "preferences_video_loop_label": "Uvek ponavljaj: ", + "preferences_autoplay_label": "Samopuštanje: ", + "preferences_continue_label": "Uvek podrazumevano puštaj sledeće: ", + "preferences_continue_autoplay_label": "Samopuštanje sledećeg video zapisa: ", + "preferences_listen_label": "Uvek podrazumevano uključen samo zvuk: ", + "preferences_local_label": "Prikaz video zapisa preko posrednika: ", + "Playlist privacy": "Podešavanja privatnosti plej liste", + "Editing playlist `x`": "Izmena plej liste `x`", + "Please sign in using 'Log in with Google'": "Molimo Vas da se prijavite pomoću 'Log in with Google'", + "Playlist does not exist.": "Nepostojeća plej lista.", + "Erroneous challenge": "Pogrešan izazov", + "Maltese": "Malteški", + "Download": "Preuzmi", + "Download as: ": "Preuzmi kao: ", + "Quota exceeded, try again in a few hours": "Kvota je premašena, molimo vas da pokušate ponovo za par sati", + "Bangla": "Bangla/Bengalski", + "preferences_quality_dash_label": "Preferirani kvalitet DASH video formata: ", + "Token manager": "Upravljanje žetonima", + "Token": "Žeton", "`x` tokens": { - "([^.,0-9]|^)1([^.,0-9]|$)": "`x` токен", - "": "`x` токена" + "([^.,0-9]|^)1([^.,0-9]|$)": "`x` žeton", + "": "`x` žetona" }, - "Import/export": "Увези/Извези", - "revoke": "опозови", + "Import/export": "Uvezi/Izvezi", + "revoke": "opozovi", "`x` unseen notifications": { - "": "`x` непрегледаних обавештења", - "([^.,0-9]|^)1([^.,0-9]|$)": "`x` непрегледаних обавештења" + "": "`x` nepregledanih obaveštenja", + "([^.,0-9]|^)1([^.,0-9]|$)": "`x` nepregledanо obaveštenjе" }, - "search": "претрага", - "Log out": "Одјава", - "Source available here.": "Изворни код.", - "Trending": "У тренду", - "Updated `x` ago": "Ажурирано пре `x`", - "Delete playlist `x`?": "Обриши плеј листу `x`?", - "Create playlist": "Направи плеј листу", - "Show less": "Прикажи мање", - "Switch Invidious Instance": "Промени Invidious инстанцу", - "Hide annotations": "Сакриј напомене", - "User ID is a required field": "Кориснички ID је обавезно поље", - "Wrong username or password": "Погрешно корисничко име или лозинка", - "Please log in": "Молимо Вас да се пријавите", - "channel:`x`": "канал:`x`", - "Could not fetch comments": "Узимање коментара није успело", + "search": "pretraga", + "Log out": "Odjava", + "Source available here.": "Izvorna koda je ovde dostupna.", + "Trending": "U trendu", + "Updated `x` ago": "Ažurirano pre `x`", + "Delete playlist `x`?": "Obriši plej listu `x`?", + "Create playlist": "Napravi plej listu", + "Show less": "Prikaži manje", + "Switch Invidious Instance": "Promeni Invidious instancu", + "Hide annotations": "Sakrij napomene", + "User ID is a required field": "Korisnički ID je obavezno polje", + "Wrong username or password": "Pogrešno korisničko ime ili lozinka", + "Please log in": "Molimo vas da se prijavite", + "channel:`x`": "kanal:`x`", + "Could not fetch comments": "Uzimanje komentara nije uspelo", "`x` points": { - "([^.,0-9]|^)1([^.,0-9]|$)": "`x` поен", - "": "`x` поена" + "([^.,0-9]|^)1([^.,0-9]|$)": "`x` poen", + "": "`x` poena" }, - "Could not create mix.": "Прављење микса није успело.", - "Empty playlist": "Празна плеј листа", - "Not a playlist.": "Није плеј листа.", - "Could not pull trending pages.": "Учитавање 'У току' странције није успело.", - "Token is expired, please try again": "Токен је истекао, молимо Вас да покушате поново", - "English (auto-generated)": "Енглески (аутоматски генерисано)", - "Afrikaans": "Африканс", - "Albanian": "Албански", - "Armenian": "Јерменски", - "Azerbaijani": "Азербејџански", - "Basque": "Баскијски", - "Bosnian": "Српски (Босна и Херцеговина)", - "Bulgarian": "Бугарски", - "Burmese": "Бурмански", - "Catalan": "Каталонски", - "Cebuano": "Cebuano", - "Chinese (Traditional)": "Кинески (Традиционални)", - "Corsican": "Корзикански", - "Danish": "Дански", - "Kannada": "Канада (Језик)", - "Kazakh": "Казашки", - "Russian": "Руски", - "Scottish Gaelic": "Шкотски Гелски", - "Sinhala": "Синхалешки", - "Slovak": "Словачки", - "Spanish": "Шпански", - "Spanish (Latin America)": "Шпански (Јужна Америка)", - "Sundanese": "Сундски", - "Swedish": "Шведски", - "Tajik": "Таџички", - "Telugu": "Телугу", - "Turkish": "Турски", - "Ukrainian": "Украјински", - "Urdu": "Урду", - "Uzbek": "Узбечки", - "Vietnamese": "Вијетнамски", + "Could not create mix.": "Pravljenje miksa nije uspelo.", + "Empty playlist": "Prazna plej lista", + "Not a playlist.": "Nije plej lista.", + "Could not pull trending pages.": "Učitavanje 'U toku' stranica nije uspelo.", + "Token is expired, please try again": "Žeton je istekao, molimo vas da pokušate ponovo", + "English (auto-generated)": "Engleski (automatski generisano)", + "Afrikaans": "Afrikans", + "Albanian": "Albanski", + "Armenian": "Jermenski", + "Azerbaijani": "Azerbejdžanski", + "Basque": "Baskijski", + "Bosnian": "Bosanski", + "Bulgarian": "Bugarski", + "Burmese": "Burmanski", + "Catalan": "Katalonski", + "Cebuano": "Sebuano", + "Chinese (Traditional)": "Kineski (Tradicionalni)", + "Corsican": "Korzikanski", + "Danish": "Danski", + "Kannada": "Kanada (Jezik)", + "Kazakh": "Kazaški", + "Russian": "Ruski", + "Scottish Gaelic": "Škotski Gelski", + "Sinhala": "Sinhaleški", + "Slovak": "Slovački", + "Spanish": "Španski", + "Spanish (Latin America)": "Španski (Južna Amerika)", + "Sundanese": "Sundski", + "Swedish": "Švedski", + "Tajik": "Tadžički", + "Telugu": "Telugu", + "Turkish": "Turski", + "Ukrainian": "Ukrajinski", + "Urdu": "Urdu", + "Uzbek": "Uzbečki", + "Vietnamese": "Vijetnamski", "`x` minutes": { - "": "`x` минута", - "([^.,0-9]|^)1([^.,0-9]|$)": "`x` минут" + "": "`x` minuta", + "([^.,0-9]|^)1([^.,0-9]|$)": "`x` minut" }, "`x` seconds": { - "": "`x` секунди", - "([^.,0-9]|^)1([^.,0-9]|$)": "`x` секунда" + "": "`x` sekundi", + "([^.,0-9]|^)1([^.,0-9]|$)": "`x` sekunda" }, - "Rating: ": "Оцена/е: ", - "View as playlist": "Погледај као плеј листу", - "Default": "Подразумеван/о", - "Gaming": "Гејминг", - "Movies": "Филмови", + "Rating: ": "Ocena/e: ", + "View as playlist": "Pogledaj kao plej listu", + "Default": "Podrazumevan/o", + "Gaming": "Igrice", + "Movies": "Filmovi", "%A %B %-d, %Y": "%A %B %-d, %Y", - "(edited)": "(измењено)", - "YouTube comment permalink": "YouTube коментар трајна веза", - "Audio mode": "Аудио мод", - "Playlists": "Плеј листе", - "relevance": "Ревелантно", - "rating": "Оцене", - "date": "Датум отпремања", - "views": "Број прегледа", - "`x` marked it with a ❤": "`x` је означио/ла ово са ❤", - "duration": "Трајање", - "features": "Карактеристике", - "hour": "Последњи сат", - "week": "Ове недеље", - "month": "Овај месец", - "year": "Ове године", - "video": "Видео", - "playlist": "Плеј листа", - "movie": "Филм", - "long": "Дуг (> 20 минута)", + "(edited)": "(izmenjeno)", + "YouTube comment permalink": "YouTube komentar trajna veza", + "Audio mode": "Audio mod", + "Playlists": "Plej liste", + "relevance": "Relevantnost", + "rating": "Ocene", + "date": "Datum otpremanja", + "views": "Broj pregleda", + "`x` marked it with a ❤": "`x` je označio/la ovo sa ❤", + "duration": "Trajanje", + "features": "Karakteristike", + "hour": "Poslednji sat", + "week": "Ove sedmice", + "month": "Ovaj mesec", + "year": "Ove godine", + "video": "Video", + "playlist": "Plej lista", + "movie": "Film", + "long": "Dugo (> 20 minuta)", "hd": "HD", - "creative_commons": "Creative Commons (Лиценца)", - "3d": "3Д", - "hdr": "Видео високе резолуције", - "filter": "Филтер", - "next_steps_error_message": "Можете урадити нешта од следећег: ", - "next_steps_error_message_go_to_youtube": "Иди на YouTube", - "footer_documentation": "Документација", - "preferences_region_label": "Држава порекла садржаја: ", - "preferences_player_style_label": "Стил плејера: ", - "preferences_dark_mode_label": "Изглед/Тема: ", - "light": "светла", - "preferences_thin_mode_label": "Компактни режим: ", - "preferences_category_misc": "Остала подешавања", - "preferences_automatic_instance_redirect_label": "Аутоматско пребацивање на другу инстанцу у случају отказивања (пређи на redirect.invidious.io): ", - "alphabetically - reverse": "по алфабету - обрнуто", - "Enable web notifications": "Омогући обавештења у веб претраживачу", - "`x` is live": "`x` преноси уживо", - "Manage tokens": "Управљај токенима", - "Watch history": "Историја гледања", - "preferences_feed_menu_label": "Feed мени: ", - "preferences_show_nick_label": "Прикажи надимке на врху: ", - "CAPTCHA enabled: ": "CAPTCHA омогућена: ", - "Registration enabled: ": "Регистрација омогућена: ", - "Subscription manager": "Менаџер претплата", - "Wilson score: ": "Wilson скор: ", - "Engagement: ": "Ангажовање: ", - "Whitelisted regions: ": "Дозвољени региони: ", - "Shared `x`": "Подељено `x`", + "creative_commons": "Creative Commons (Licenca)", + "3d": "3D", + "hdr": "Video Visoke Rezolucije", + "filter": "Filter", + "next_steps_error_message": "Nakon čega bi trebali probati: ", + "next_steps_error_message_go_to_youtube": "Idi na YouTube", + "footer_documentation": "Dokumentacija", + "preferences_region_label": "Država porekla sadržaja: ", + "preferences_player_style_label": "Stil plejera: ", + "preferences_dark_mode_label": "Izgled/Tema: ", + "light": "svetlo", + "preferences_thin_mode_label": "Kompaktni režim: ", + "preferences_category_misc": "Ostala podešavanja", + "preferences_automatic_instance_redirect_label": "Automatsko prebacivanje na drugu instancu u slučaju otkazivanja (preči će nazad na redirect.invidious.io): ", + "alphabetically - reverse": "po alfabetu - obrnuto", + "Enable web notifications": "Omogući obaveštenja u veb pretraživaču", + "`x` is live": "`x` prenosi uživo", + "Manage tokens": "Upravljaj žetonima", + "Watch history": "Istorija gledanja", + "preferences_feed_menu_label": "Dovodna stranica: ", + "preferences_show_nick_label": "Prikaži nadimke na vrhu: ", + "CAPTCHA enabled: ": "CAPTCHA omogućena: ", + "Registration enabled: ": "Registracija omogućena: ", + "Subscription manager": "Upravljanje praćenjima", + "Wilson score: ": "Wilsonova ocena: ", + "Engagement: ": "Angažovanje: ", + "Whitelisted regions: ": "Dozvoljene oblasti: ", + "Shared `x`": "Podeljeno `x`", "`x` views": { - "": "`x` прегледа", - "([^.,0-9]|^)1([^.,0-9]|$)": "`x` преглед" + "": "`x` pregleda", + "([^.,0-9]|^)1([^.,0-9]|$)": "`x` pregled" }, - "Premieres in `x`": "Премијера у `x`", - "Premieres `x`": "Премијере у `x`", - "Hi! Looks like you have JavaScript turned off. Click here to view comments, keep in mind they may take a bit longer to load.": "Хеј! Изгледа да сте онемогућили JavaScript. Кликните овде да видите коментаре, имајте на уму да ово може да потраје дуже док се не учитају.", + "Premieres in `x`": "Premera u `x`", + "Premieres `x`": "Premere u `x`", + "Hi! Looks like you have JavaScript turned off. Click here to view comments, keep in mind they may take a bit longer to load.": "Hej! Izgleda da ste onemogućili JavaScript. Kliknite ovde da vidite komentare, čuvajte na umu da ovo može da potraje duže dok se ne učitaju.", "View `x` comments": { - "([^.,0-9]|^)1([^.,0-9]|$)": "Прикажи `x` коментар", - "": "Прикажи `x` коментара" + "([^.,0-9]|^)1([^.,0-9]|$)": "Prikaži `x` komentar", + "": "Prikaži `x` komentara" }, - "View Reddit comments": "Прикажи Reddit коментаре", - "Unable to log in, make sure two-factor authentication (Authenticator or SMS) is turned on.": "Неуспешна пријава, проверите да ли сте упалили двофакторну аутентикацију (Аутентикатор или СМС).", - "CAPTCHA is a required field": "CAPTCHA је обавезно поље", - "Croatian": "Хрватски", - "Estonian": "Естонски", - "Filipino": "Филипино", - "French": "Француски", - "Galician": "Галицијски", - "German": "Немачки", - "Greek": "Грчки", - "Hausa": "Хауса", - "Italian": "Италијански", - "Khmer": "Кмерски", - "Kurdish": "Курдски", - "Kyrgyz": "Киргиски", - "Latvian": "Летонски", - "Lithuanian": "Литвански", - "Macedonian": "Македонски", - "Malagasy": "Малгашки", - "Malay": "Малајски", - "Marathi": "Маратхи", - "Mongolian": "Монголски", - "Norwegian Bokmål": "Норвешки Бокмал", - "Nyanja": "Чева", - "Pashto": "Паштунски", - "Persian": "Персијски", - "Punjabi": "Пунџаби", - "Romanian": "Румунски", - "Welsh": "Велшки", - "Western Frisian": "Западнофризијски", + "View Reddit comments": "Prikaži Reddit komentare", + "Unable to log in, make sure two-factor authentication (Authenticator or SMS) is turned on.": "Neuspešna prijava, proverite da li ste upalili dvofaktornu autentikaciju (Autentikator ili SMS).", + "CAPTCHA is a required field": "CAPTCHA je obavezno polje", + "Croatian": "Hrvatski", + "Estonian": "Estonski", + "Filipino": "Filipino", + "French": "Francuski", + "Galician": "Galicijski", + "German": "Nemački", + "Greek": "Grčki", + "Hausa": "Hausa", + "Italian": "Talijanski", + "Khmer": "Kmerski", + "Kurdish": "Kurdski", + "Kyrgyz": "Kirgiski", + "Latvian": "Letonski", + "Lithuanian": "Litvanski", + "Macedonian": "Makedonski", + "Malagasy": "Malgaški", + "Malay": "Malajski", + "Marathi": "Marathi", + "Mongolian": "Mongolski", + "Norwegian Bokmål": "Norveški Bokmal", + "Nyanja": "Čeva", + "Pashto": "Paštunski", + "Persian": "Persijski", + "Punjabi": "Pundžabi", + "Romanian": "Rumunski", + "Welsh": "Velški", + "Western Frisian": "Zapadnofrizijski", "`x` years": { - "": "`x` година", - "([^.,0-9]|^)1([^.,0-9]|$)": "`x` година" + "": "`x` godina", + "([^.,0-9]|^)1([^.,0-9]|$)": "`x` godina" }, "`x` weeks": { - "": "`x` недеља", - "([^.,0-9]|^)1([^.,0-9]|$)": "`x` недеља" + "": "`x` sedmica", + "([^.,0-9]|^)1([^.,0-9]|$)": "`x` sedmica" }, "`x` days": { - "([^.,0-9]|^)1([^.,0-9]|$)": "`x` дан", - "": "`x` дана" + "([^.,0-9]|^)1([^.,0-9]|$)": "`x` dan", + "": "`x` dana" }, "`x` hours": { - "([^.,0-9]|^)1([^.,0-9]|$)": "`x` сат", - "": "`x` сати" + "([^.,0-9]|^)1([^.,0-9]|$)": "`x` sat", + "": "`x` sati" }, - "Fallback comments: ": "Коментари у случају отказивања: ", - "Popular": "Популарно", - "Search": "Претрага", - "About": "О програму", - "footer_source_code": "Изворни Код", - "footer_original_source_code": "Оригинални изворни код", - "preferences_related_videos_label": "Прикажи сличне видео клипове: ", - "preferences_annotations_label": "Прикажи напомене подразумевано: ", - "preferences_extend_desc_label": "Аутоматски прикажи цео опис видеа: ", - "preferences_vr_mode_label": "Интерактивни видео клипови у 360 степени: ", - "preferences_category_visual": "Визуелне преференце", - "preferences_captions_label": "Подразумевани титл: ", - "Music": "Музика", - "content_type": "Тип", - "Broken? Try another Invidious Instance": "Не функционише исправно? Пробајте другу Invidious инстанцу", - "Tamil": "Тамилски", - "Save preferences": "Сачувај подешавања", - "Only show latest unwatched video from channel: ": "Прикажи само последње видео клипове који нису погледани са канала: ", - "Xhosa": "Коса (Језик)", - "channel": "Канал", - "Hungarian": "Мађарски", - "Maori": "Маори (Језик)", - "Manage subscriptions": "Управљај претплатама", - "Hindi": "Хинди", - "`x` ago": "пре `x`", - "Import/export data": "Увези/Извези податке", - "`x` uploaded a video": "`x` је отпремио/ла видео клип", + "Fallback comments: ": "Komentari u slučaju otkazivanja: ", + "Popular": "Popularno", + "Search": "Pretraga", + "About": "O programu", + "footer_source_code": "Izvorna Koda", + "footer_original_source_code": "Originalna Izvorna Koda", + "preferences_related_videos_label": "Prikaži slične video klipove: ", + "preferences_annotations_label": "Prikaži napomene podrazumevano: ", + "preferences_extend_desc_label": "Automatski prikaži ceo opis videa: ", + "preferences_vr_mode_label": "Interaktivni video klipovi u 360 stepeni: ", + "preferences_category_visual": "Vizuelne preference", + "preferences_captions_label": "Podrazumevani titl: ", + "Music": "Muzika", + "content_type": "Tip", + "Broken? Try another Invidious Instance": "Ne funkcioniše ispravno? Probajte drugu Invidious instancu", + "Tamil": "Tamilski", + "Save preferences": "Sačuvaj podešavanja", + "Only show latest unwatched video from channel: ": "Prikaži samo poslednje video klipove koji nisu pogledani sa kanala: ", + "Xhosa": "Kosa (Jezik)", + "channel": "Kanal", + "Hungarian": "Mađarski", + "Maori": "Maori (Jezik)", + "Manage subscriptions": "Upravljaj zapisima", + "Hindi": "Hindi", + "`x` ago": "pre `x`", + "Import/export data": "Uvezi/Izvezi podatke", + "`x` uploaded a video": "`x` je otpremio/la video klip", "View `x` replies": { - "([^.,0-9]|^)1([^.,0-9]|$)": "Погледај `x` одоговор", - "": "Погледај `x` одоговора" + "([^.,0-9]|^)1([^.,0-9]|$)": "Prikaži `x` odgovor", + "": "Prikaži `x` odgovora" }, - "Delete account": "Обриши налог", - "preferences_default_home_label": "Подразумевана почетна страница: ", - "Serbian": "Српски", - "License: ": "Лиценца: ", - "live": "Уживо", - "Report statistics: ": "Извештавај о статистици: ", - "Only show latest video from channel: ": "Приказуј последње видео клипове само са канала: ", - "channel name - reverse": "име канала - обрнуто", - "Could not get channel info.": "Узимање података о каналу није успело.", - "View privacy policy.": "Погледај политику приватности.", - "Change password": "Промени лозинку", - "Malayalam": "Малајалам", - "View more comments on Reddit": "Прикажи више коментара на Reddit-у", - "Portuguese": "Португалски", - "View YouTube comments": "Прикажи YouTube коментаре", - "published - reverse": "објављено - обрнуто", - "Dutch": "Белгијски", - "preferences_volume_label": "Јачина звука: ", - "preferences_locale_label": "Језик: ", + "Delete account": "Obriši nalog", + "preferences_default_home_label": "Podrazumevana početna stranica: ", + "Serbian": "Srpski", + "License: ": "Licenca: ", + "live": "Uživo", + "Report statistics: ": "Izveštavaj o statistici: ", + "Only show latest video from channel: ": "Prikazuj poslednje video klipove samo sa kanala: ", + "channel name - reverse": "ime kanala - obrnuto", + "Could not get channel info.": "Uzimanje podataka o kanalu nije uspelo.", + "View privacy policy.": "Pogledaj izveštaj o privatnosti.", + "Change password": "Promeni lozinku", + "Malayalam": "Malajalam", + "View more comments on Reddit": "Prikaži više komentara na Reddit-u", + "Portuguese": "Portugalski", + "View YouTube comments": "Prikaži YouTube komentare", + "published - reverse": "objavljeno - obrnuto", + "Dutch": "Holandski", + "preferences_volume_label": "Jačina zvuka: ", + "preferences_locale_label": "Jezik: ", "`x` subscriptions": { - "([^.,0-9]|^)1([^.,0-9]|$)": "`x` пратиоц", - "": "`x` пратилаца" + "([^.,0-9]|^)1([^.,0-9]|$)": "`x` praćenje", + "": "`x` praćenja" }, - "adminprefs_modified_source_code_url_label": "URL веза до репозиторијума са измењеним изворним кодом", - "Community": "Заједница", - "Video mode": "Видео мод", - "Fallback captions: ": "Титл у случају да главни није доступан: ", - "Private": "Приватно", - "alphabetically": "по алфабету", - "No such user": "Непостојећи корисник", - "Subscriptions": "Праћења", - "today": "Данас", - "Finnish": "Фински", - "Lao": "Лаоски", - "Login enabled: ": "Пријава омогућена: ", - "Shona": "Шона", - "location": "Локација", - "Load more": "Учитај више", - "Released under the AGPLv3 on Github.": "Избачено под лиценцом AGPLv3 на Github-у.", - "Slovenian": "Словеначки", - "View JavaScript license information.": "Погледај информације лиценце везане за JavaScript.", - "Chinese (Simplified)": "Кинески (Поједностављени)", - "preferences_comments_label": "Подразумевани коментари: ", - "Incorrect password": "Нетачна лозинка", - "Show replies": "Прикажи одговоре", - "Invidious Private Feed for `x`": "Invidious приватни Feed за `x`", - "Watch on YouTube": "Гледај на YouTube-у", - "Wrong answer": "Погрешан одговор", - "preferences_quality_label": "Преферирани видео квалитет: ", - "Hide replies": "Сакриј одговоре", - "Invalid TFA code": "Неважећи TFA код", - "Login failed. This may be because two-factor authentication is not turned on for your account.": "Неуспешна пријава! Ово се можда дешава јер двофакторна аутентикација није омогућена на Вашем налогу.", - "Erroneous CAPTCHA": "Погрешна CAPTCHA", - "Erroneous token": "Погрешан токен", - "Czech": "Чешки", - "Latin": "Латински", - "Videos": "Видео клипови", + "adminprefs_modified_source_code_url_label": "URL veza do skladišta sa Izmenjenom Izvornom Kodom", + "Community": "Zajednica", + "Video mode": "Video mod", + "Fallback captions: ": "Titl u slučaju da glavni nije dostupan: ", + "Private": "Privatno", + "alphabetically": "po alfabetu", + "No such user": "Nepostojeći korisnik", + "Subscriptions": "Praćenja", + "today": "Danas", + "Finnish": "Finski", + "Lao": "Laoski", + "Login enabled: ": "Prijava omogućena: ", + "Shona": "Šona", + "location": "Lokacija", + "Load more": "Učitaj više", + "Released under the AGPLv3 on Github.": "Izbačeno pod licencom AGPLv3 na Github-u.", + "Slovenian": "Slovenački", + "View JavaScript license information.": "Pogledaj informacije licence vezane za JavaScript.", + "Chinese (Simplified)": "Kineski (Pojednostavljeni)", + "preferences_comments_label": "Podrazumevani komentari: ", + "Incorrect password": "Netačna lozinka", + "Show replies": "Prikaži odgovore", + "Invidious Private Feed for `x`": "Invidious Privatni Dovod za `x`", + "Watch on YouTube": "Gledaj na YouTube-u", + "Wrong answer": "Pogrešan odgovor", + "preferences_quality_label": "Preferirani video kvalitet: ", + "Hide replies": "Sakrij odgovore", + "Invalid TFA code": "Nevažeća TFA koda", + "Login failed. This may be because two-factor authentication is not turned on for your account.": "Neuspešna prijava! Ovo se možda dešava jer dvofaktorna autentikacija nije omogućena na vašem nalogu.", + "Erroneous CAPTCHA": "Pogrešna CAPTCHA", + "Erroneous token": "Pogrešan žeton", + "Czech": "Češki", + "Latin": "Latinski", + "Videos": "Video klipovi", "4k": "4К", - "footer_donate_page": "Донирај", - "English": "Енглески", - "Arabic": "Арапски", - "Unlisted": "Ненаведено", - "Hidden field \"challenge\" is a required field": "Сакривено \"challenge\" поље је обавезно", - "Hidden field \"token\" is a required field": "Сакривено \"token\" поље је обавезно", - "Georgian": "Грузијски", - "Hawaiian": "Хавајски", - "Hebrew": "Хебрејски", - "Icelandic": "Исландски", - "Igbo": "Игбо", - "Japanese": "Јапански", - "Javanese": "Јавански", - "Sindhi": "Синди", - "Swahili": "Свахили", - "Yiddish": "Јидиш", - "Zulu": "Зулу", - "subtitles": "Титл", - "Password cannot be longer than 55 characters": "Лозинка не може бити дужа од 55 карактера", - "This channel does not exist.": "Овај канал не постоји.", - "Belarusian": "Белоруски", - "Gujarati": "Гуџарати", - "Haitian Creole": "Хаићански Креолски", - "Somali": "Сомалијски", - "Top": "Врх", - "footer_modfied_source_code": "Измењени изворни код", - "preferences_category_subscription": "Подешавања праћења", - "preferences_annotations_subscribed_label": "Подразумевано приказати напомене за канале које пратите? ", - "preferences_max_results_label": "Број видео клипова приказаних у листи (feed-у): ", - "preferences_sort_label": "Сортирај видео клипове по: ", - "preferences_unseen_only_label": "Прикажи само видео клипове који нису погледани: ", - "preferences_notifications_only_label": "Прикажи само обавештења (ако их уопште има): ", - "preferences_category_data": "Подешавања података", - "Clear watch history": "Обриши историју гледања", - "preferences_category_admin": "Администраторска подешавања", - "published": "објављено", - "sort": "Сортирај према", - "show": "Шоу", - "short": "Кратак (< 4 минута)", - "Current version: ": "Тренутна верзија: ", - "Top enabled: ": "Врх омогућен: ", - "Public": "Јавно", - "Delete playlist": "Обриши плеј листу", - "Title": "Наслов", - "Show annotations": "Прикажи напомене", - "Password cannot be empty": "Лозинка не може бити празна", - "Deleted or invalid channel": "Обрисан или непостојећи канал", - "Esperanto": "Есперанто", - "Hmong": "Хмонг", - "Luxembourgish": "Луксембуршки", - "Nepali": "Непалски", - "Samoan": "Самоански", - "News": "Вести", - "permalink": "трајна веза", - "Password is a required field": "Лозинка је обавезно поље", - "Amharic": "Амхарски", - "Indonesian": "Индонежански", - "Irish": "Ирски", - "Korean": "Корејски", - "Southern Sotho": "Сото", - "Thai": "Тајски", + "footer_donate_page": "Doniraj", + "English": "Engleski", + "Arabic": "Arapski", + "Unlisted": "Nenavedeno", + "Hidden field \"challenge\" is a required field": "Sakriveno \"challenge\" polje je obavezno", + "Hidden field \"token\" is a required field": "Sakriveno \"token\" polje je obavezno", + "Georgian": "Gruzijski", + "Hawaiian": "Havajski", + "Hebrew": "Hebrejski", + "Icelandic": "Islandski", + "Igbo": "Igbo", + "Japanese": "Japanski", + "Javanese": "Javanski", + "Sindhi": "Sindi", + "Swahili": "Svahili", + "Yiddish": "Jidiš", + "Zulu": "Zulu", + "subtitles": "Titl/Prevod", + "Password cannot be longer than 55 characters": "Lozinka ne može biti duža od 55 karaktera", + "This channel does not exist.": "Ovaj kanal ne postoji.", + "Belarusian": "Beloruski", + "Gujarati": "Gudžarati", + "Haitian Creole": "Haićanski Kreolski", + "Somali": "Somalijski", + "Top": "Vrh", + "footer_modfied_source_code": "Izmenjena Izvorna Koda", + "preferences_category_subscription": "Podešavanja praćenja", + "preferences_annotations_subscribed_label": "Podrazumevano prikazati napomene za kanale koje pratite? ", + "preferences_max_results_label": "Broj video klipova prikazanih u dovodnoj listi: ", + "preferences_sort_label": "Sortiraj video klipove po: ", + "preferences_unseen_only_label": "Prikaži samo video klipove koji nisu pogledani: ", + "preferences_notifications_only_label": "Prikaži samo obaveštenja (ako ih uopšte ima): ", + "preferences_category_data": "Podešavanja podataka", + "Clear watch history": "Obriši istoriju gledanja", + "preferences_category_admin": "Administratorska podešavanja", + "published": "objavljeno", + "sort": "Poredaj prema", + "show": "Emisija", + "short": "Kratko (< 4 minute)", + "Current version: ": "Trenutna verzija: ", + "Top enabled: ": "Vrh omogućen: ", + "Public": "Javno", + "Delete playlist": "Obriši plej listu", + "Title": "Naslov", + "Show annotations": "Prikaži napomene", + "Password cannot be empty": "Lozinka ne može biti prazna", + "Deleted or invalid channel": "Obrisan ili nepostojeći kanal", + "Esperanto": "Esperanto", + "Hmong": "Hmong", + "Luxembourgish": "Luksemburški", + "Nepali": "Nepalski", + "Samoan": "Samoanski", + "News": "Vesti", + "permalink": "trajna veza", + "Password is a required field": "Lozinka je obavezno polje", + "Amharic": "Amharski", + "Indonesian": "Indonežanski", + "Irish": "Irski", + "Korean": "Korejski", + "Southern Sotho": "Južni Soto", + "Thai": "Tajski", "`x` months": { - "([^.,0-9]|^)1([^.,0-9]|$)": "`x` месец", - "": "`x` месеци" + "([^.,0-9]|^)1([^.,0-9]|$)": "`x` mesec", + "": "`x` meseci" }, - "preferences_speed_label": "Подразумевана брзина: ", - "Dark mode: ": "Тамни режим: ", - "dark": "тамна", - "Redirect homepage to feed: ": "Пребаци са почетне странице на листу (feed): ", - "channel name": "име канала", - "View all playlists": "Прегледај све плеј листе", - "Show more": "Прикажи више", - "Genre: ": "Жанр: ", - "Family friendly? ": "Погодно за породицу? ", - "next_steps_error_message_refresh": "Освежи страницу", + "preferences_speed_label": "Podrazumevana brzina: ", + "Dark mode: ": "Tamni režim: ", + "dark": "tamno", + "Redirect homepage to feed: ": "Prebaci sa početne stranice na dovodnu listu: ", + "channel name": "ime kanala", + "View all playlists": "Pregledaj sve plej liste", + "Show more": "Prikaži više", + "Genre: ": "Žanr: ", + "Family friendly? ": "Pogodno za porodicu? ", + "next_steps_error_message_refresh": "Osveži stranicu", "youtube": "YouTube", "reddit": "Reddit", - "unsubscribe": "прекини са праћењем", - "Blacklisted regions: ": "Блокирани региони: ", - "Polish": "Пољски", - "Yoruba": "Јоруба" + "unsubscribe": "prekini sa praćenjem", + "Blacklisted regions: ": "Zabranjene oblasti: ", + "Polish": "Poljski", + "Yoruba": "Joruba" } diff --git a/locales/sr_Cyrl.json b/locales/sr_Cyrl.json index 3a6d6c0b..628fab85 100644 --- a/locales/sr_Cyrl.json +++ b/locales/sr_Cyrl.json @@ -1,170 +1,437 @@ { "`x` subscribers": { - "": "`x` пратилац" + "": "`x` пратилацa", + "([^.,0-9]|^)1([^.,0-9]|$)": "`x` пратилац" }, "`x` videos": { - "": "`x` видеа" + "": "`x` видео записа", + "([^.,0-9]|^)1([^.,0-9]|$)": "`x` видео запис" }, "`x` playlists": { - "": "`x` плејлиста/е" + "": "`x` списака извођења", + "([^.,0-9]|^)1([^.,0-9]|$)": "`x` списак извођења" }, "LIVE": "УЖИВО", - "Shared `x` ago": "Објављено пре `x`", - "Unsubscribe": "Прекините праћење", - "Subscribe": "Пратите", - "View channel on YouTube": "Погледајте канал на YouTube-у", - "View playlist on YouTube": "Погледајте плејлисту на YouTube-у", + "Shared `x` ago": "Подељено пре `x`", + "Unsubscribe": "Прекини праћење", + "Subscribe": "Прати", + "View channel on YouTube": "Погледај канал на YouTube-у", + "View playlist on YouTube": "Погледај списак извођења на YоуТубе-у", "newest": "најновије", "oldest": "најстарије", "popular": "популарно", "last": "последње", "Next page": "Следећа страна", "Previous page": "Претходна страна", - "Clear watch history?": "Обришите историју прегледања?", + "Clear watch history?": "Избрисати повест прегледања?", "New password": "Нова лозинка", - "New passwords must match": "Нове лозинке се морају поклапати", + "New passwords must match": "Нове лозинке морају бити истоветне", "Cannot change password for Google accounts": "Није могуће променити лозинку за Google налоге", - "Authorize token?": "Овластите токен?", - "Authorize token for `x`?": "Овластите токен за `x`?", + "Authorize token?": "Овласти жетон?", + "Authorize token for `x`?": "Овласти жетон за `x`?", "Yes": "Да", "No": "Не", "Import and Export Data": "Увоз и извоз података", - "Import": "Увезите", - "Import Invidious data": "Увезите Invidious податке", - "Import YouTube subscriptions": "Увезите праћења са YouTube-а", - "Import FreeTube subscriptions (.db)": "Увезите праћења са FreeTube-а (.db)", - "Import NewPipe subscriptions (.json)": "Увезите праћења са NewPipe-а (.json)", - "Import NewPipe data (.zip)": "Увезите NewPipe податке (.zip)", - "Export": "Извезите", - "Export subscriptions as OPML": "Извезите праћења у OPML формату", - "Export subscriptions as OPML (for NewPipe & FreeTube)": "Извезите праћења у OPML формату (за NewPipe и FreeTube )", - "Export data as JSON": "Изветизе податке у JSON формату", + "Import": "Увези", + "Import Invidious data": "Увези податке са Individious-а", + "Import YouTube subscriptions": "Увези праћења са YouTube-а", + "Import FreeTube subscriptions (.db)": "Увези праћења са FreeTube-а (.db)", + "Import NewPipe subscriptions (.json)": "Увези праћења са NewPipe-а (.json)", + "Import NewPipe data (.zip)": "Увези податке са NewPipe-a (.zip)", + "Export": "Извези", + "Export subscriptions as OPML": "Извези праћења као ОПМЛ датотеку", + "Export subscriptions as OPML (for NewPipe & FreeTube)": "Извези праћења као ОПМЛ датотеку (за NewPipe и FreeTube)", + "Export data as JSON": "Извези податке као JSON датотеку", "Delete account?": "Избришите налог?", "History": "Историја", - "An alternative front-end to YouTube": "Алтернативни фронтенд за YouTube", - "JavaScript license information": "Извештај о JavaScript лиценци", + "An alternative front-end to YouTube": "Заменски кориснички слој за YouTube", + "JavaScript license information": "Извештај о JavaScript одобрењу", "source": "извор", - "Log in": "Пријавите се", - "Log in/register": "Пријавите се/направите налог", - "Log in with Google": "Пријавите се помоћу Google-а", - "User ID": "ИД корисника", + "Log in": "Пријави се", + "Log in/register": "Пријави се/Отворите налог", + "Log in with Google": "Пријави се помоћу Google-а", + "User ID": "Кориснички ИД", "Password": "Лозинка", - "Time (h:mm:ss):": "Колико је сати? (ч:мм:сс):", - "Text CAPTCHA": "Текстуална CAPTCHA", - "Image CAPTCHA": "Сликовна CAPTCHA", - "Sign In": "Пријавите се", - "Register": "Направите налог", + "Time (h:mm:ss):": "Време (ч:мм:сс):", + "Text CAPTCHA": "Знаковни ЦАПТЧА", + "Image CAPTCHA": "Сликовни CAPTCHA", + "Sign In": "Пријава", + "Register": "Отвори налог", "E-mail": "Е-пошта", - "Google verification code": "Google верификациони кôд", + "Google verification code": "Google-ова оверна кода", "Preferences": "Подешавања", - "preferences_category_player": "Подешавања видео плејера", + "preferences_category_player": "Подешавања репродуктора", "preferences_video_loop_label": "Увек понављај: ", - "preferences_autoplay_label": "Аутоматско пуштање: ", - "preferences_continue_label": "Увек пуштај следеће: ", - "preferences_continue_autoplay_label": "Аутоматско пуштање следећег видеа: ", - "preferences_listen_label": "Режим слушања као подразумевано: ", - "preferences_local_label": "Пуштање видеа кроз прокси сервер: ", - "preferences_speed_label": "Подразумевана брзина репродукције: ", - "preferences_quality_label": "Претпостављени квалитет видеа: ", + "preferences_autoplay_label": "Самопуштање: ", + "preferences_continue_label": "Увек подразумевано пуштај следеће: ", + "preferences_continue_autoplay_label": "Самопуштање следећег видео записа: ", + "preferences_listen_label": "Увек подразумевано укључен само звук: ", + "preferences_local_label": "Приказ видео записа преко посредника: ", + "preferences_speed_label": "Подразумевана брзина: ", + "preferences_quality_label": "Преферирани видео квалитет: ", "preferences_volume_label": "Јачина звука: ", "preferences_comments_label": "Подразумевани коментари: ", - "youtube": "са YouTube-а", - "reddit": "са редита", - "preferences_captions_label": "Подразумевани титлови: ", - "Fallback captions: ": "Алтернативни титлови: ", - "preferences_related_videos_label": "Прикажи сличне видее: ", - "preferences_annotations_label": "Увек приказуј анотације: ", - "preferences_category_visual": "Подешавања изгледа", + "youtube": "YouTube", + "reddit": "Reddit", + "preferences_captions_label": "Подразумевани титл: ", + "Fallback captions: ": "Титл у случају да главни није доступан: ", + "preferences_related_videos_label": "Прикажи сличне видео клипове: ", + "preferences_annotations_label": "Прикажи напомене подразумевано: ", + "preferences_category_visual": "Визуелне преференце", "preferences_player_style_label": "Стил плејера: ", "Dark mode: ": "Тамни режим: ", - "preferences_dark_mode_label": "Тема: ", - "dark": "тамна", - "light": "светла", - "preferences_thin_mode_label": "Узани режим: ", - "preferences_category_subscription": "Подешавања о праћењима", - "preferences_annotations_subscribed_label": "Увек приказуј анотације за канале које пратим: ", - "Redirect homepage to feed: ": "Прикажи праћења као почетну страницу: ", - "preferences_max_results_label": "Количина приказаних видеа на доводу: ", - "preferences_sort_label": "Сортирај према: ", - "published": "датуму објављивања", - "published - reverse": "датуму објављивања - обрнуто", - "alphabetically": "алфабету", - "alphabetically - reverse": "алфабету - обрнуто", - "channel name": "називу канала", - "channel name - reverse": "називу канала - обрнуто", - "Only show latest video from channel: ": "Прикажи само најновији видео са канала: ", - "Only show latest unwatched video from channel: ": "Прикажи само најновији негледани видео са канала: ", - "preferences_unseen_only_label": "Прикажи само негледано: ", - "preferences_notifications_only_label": "Прикажи само обавештења (ако их има): ", - "Enable web notifications": "Укључи обавештења преко претраживача", - "`x` uploaded a video": "`x`је објавио/ла видео", - "`x` is live": "`x` емитује уживо", - "preferences_category_data": "Подешавања о подацима", - "Clear watch history": "Обришите историју прегледања", - "Import/export data": "Увезите или извезите податке", - "Change password": "Промените лозинку", - "Manage subscriptions": "Управљајте праћењима", - "Manage tokens": "Управљајте токенима", - "Watch history": "Историја прегледања", - "Delete account": "Избришите налог", - "preferences_category_admin": "Подешавања администратора", - "preferences_default_home_label": "Подразумевана главна страница: ", - "preferences_feed_menu_label": "Мени довода: ", - "CAPTCHA enabled: ": "CAPTCHA укључена?: ", - "Login enabled: ": "Пријава укључена?: ", - "Registration enabled: ": "Регистрација укључена?: ", + "preferences_dark_mode_label": "Изглед/Тема: ", + "dark": "тамно", + "light": "светло", + "preferences_thin_mode_label": "Компактни режим: ", + "preferences_category_subscription": "Подешавања праћења", + "preferences_annotations_subscribed_label": "Подразумевано приказати напомене за канале које пратите? ", + "Redirect homepage to feed: ": "Пребаци са почетне странице на доводну листу: ", + "preferences_max_results_label": "Број видео клипова приказаних у доводној листи: ", + "preferences_sort_label": "Сортирај видео клипове по: ", + "published": "објављено", + "published - reverse": "објављено - обрнуто", + "alphabetically": "по алфабету", + "alphabetically - reverse": "по алфабету - обрнуто", + "channel name": "име канала", + "channel name - reverse": "име канала - обрнуто", + "Only show latest video from channel: ": "Приказуј последње видео клипове само са канала: ", + "Only show latest unwatched video from channel: ": "Прикажи само последње видео клипове који нису погледани са канала: ", + "preferences_unseen_only_label": "Прикажи само видео клипове који нису погледани: ", + "preferences_notifications_only_label": "Прикажи само обавештења (ако их уопште има): ", + "Enable web notifications": "Омогући обавештења у веб претраживачу", + "`x` uploaded a video": "`x` је отпремио/ла видео клип", + "`x` is live": "`x` преноси уживо", + "preferences_category_data": "Подешавања података", + "Clear watch history": "Обриши историју гледања", + "Import/export data": "Увези/Извези податке", + "Change password": "Промени лозинку", + "Manage subscriptions": "Управљај записима", + "Manage tokens": "Управљај жетонима", + "Watch history": "Историја гледања", + "Delete account": "Обриши налог", + "preferences_category_admin": "Администраторска подешавања", + "preferences_default_home_label": "Подразумевана почетна страница: ", + "preferences_feed_menu_label": "Доводна страница: ", + "CAPTCHA enabled: ": "CAPTCHA омогућена: ", + "Login enabled: ": "Пријава омогућена: ", + "Registration enabled: ": "Регистрација омогућена: ", "Save preferences": "Сачувај подешавања", "Subscription manager": "Управљање праћењима", - "Token manager": "Управљање токенима", - "Token": "Токен", + "Token manager": "Управљање жетонима", + "Token": "Жетон", "`x` subscriptions": { - "": "`x`праћења" + "": "`x` праћења", + "([^.,0-9]|^)1([^.,0-9]|$)": "`x` праћење" }, "`x` tokens": { - "": "`x`токена" + "": "`x` жетона", + "([^.,0-9]|^)1([^.,0-9]|$)": "`x` жетон" }, - "Import/export": "Увези/извези", - "unsubscribe": "укини праћење", + "Import/export": "Увези/Извези", + "unsubscribe": "прекини са праћењем", "revoke": "опозови", "Subscriptions": "Праћења", "`x` unseen notifications": { - "": "`x` непрочитаних обавештења" + "": "`x` непрочитаних обавештења", + "([^.,0-9]|^)1([^.,0-9]|$)": "`x` непрегледано обавештење" }, "search": "претрага", - "Log out": "Одјавите се", - "Source available here.": "Изворни код доступан овде.", - "View JavaScript license information.": "Прикажи информације о JavaScript лиценци.", - "View privacy policy.": "Прикажи извештај о приватности.", + "Log out": "Одјава", + "Source available here.": "Изворна кода је овде доступна.", + "View JavaScript license information.": "Погледај информације лиценце везане за JavaScript.", + "View privacy policy.": "Погледај извештај о приватности.", "Trending": "У тренду", "Public": "Јавно", - "Unlisted": "По позиву", + "Unlisted": "Ненаведено", "Private": "Приватно", - "View all playlists": "Прикажи све плејлисте", + "View all playlists": "Прегледај све плеј листе", "Updated `x` ago": "Ажурирано пре `x`", - "Delete playlist `x`?": "Избриши плејлисту `x`?", - "Delete playlist": "Избриши плејлисту", - "Create playlist": "Направи плејлисту", + "Delete playlist `x`?": "Обриши плеј листу `x`?", + "Delete playlist": "Обриши плеј листу", + "Create playlist": "Направи плеј листу", "Title": "Наслов", - "Playlist privacy": "Видљивост плејлисте", - "Editing playlist `x`": "Уређујете плејлисту `x`", - "Watch on YouTube": "Гледајте на YouTube-у", - "Hide annotations": "Сакриј анотације", - "Show annotations": "Прикажи анотације", + "Playlist privacy": "Подешавања приватности плеј листе", + "Editing playlist `x`": "Измена плеј листе `x`", + "Watch on YouTube": "Гледај на YouTube-у", + "Hide annotations": "Сакриј напомене", + "Show annotations": "Прикажи напомене", "Genre: ": "Жанр: ", "License: ": "Лиценца: ", "Engagement: ": "Ангажовање: ", "Whitelisted regions: ": "Дозвољене области: ", "Blacklisted regions: ": "Забрањене области: ", "`x` views": { - "": "`x` прегледа" + "": "`x` прегледа", + "([^.,0-9]|^)1([^.,0-9]|$)": "`x` преглед" }, - "Premieres in `x`": "Емитује се уживо за `x`", - "Hi! Looks like you have JavaScript turned off. Click here to view comments, keep in mind they may take a bit longer to load.": "Здраво! Изгледа да је искључен JavaScript. Кликните овде да бисте приказали коментаре. Требаће мало дуже да се учитају.", - "View YouTube comments": "Прикажи коментаре са YouTube-а", - "View more comments on Reddit": "Прикажи још коментара на Reddit-у", - "View Reddit comments": "Прикажи коментаре са Reddit-а", + "Premieres in `x`": "Премера у `x`", + "Hi! Looks like you have JavaScript turned off. Click here to view comments, keep in mind they may take a bit longer to load.": "Хеј! Изгледа да сте онемогућили JavaScript. Кликните овде да видите коментаре, чувајте на уму да ово може да потраје дуже док се не учитају.", + "View YouTube comments": "Прикажи YouTube коментаре", + "View more comments on Reddit": "Прикажи више коментара на Reddit-у", + "View Reddit comments": "Прикажи Reddit коментаре", "Hide replies": "Сакриј одговоре", "Show replies": "Прикажи одговоре", - "Incorrect password": "Неисправна лозинка", - "Current version: ": "Тренутна верзија: " + "Incorrect password": "Нетачна лозинка", + "Current version: ": "Тренутна верзија: ", + "Wilson score: ": "Wилсонова оцена: ", + "Burmese": "Бурмански", + "preferences_quality_dash_label": "Преферирани квалитет DASH видео формата: ", + "Erroneous token": "Погрешан жетон", + "Quota exceeded, try again in a few hours": "Квота је премашена, молимо вас да покушате поново за пар сати", + "Unable to log in, make sure two-factor authentication (Authenticator or SMS) is turned on.": "Неуспешна пријава, проверите да ли сте упалили двофакторну аутентикацију (Аутентикатор или СМС).", + "CAPTCHA is a required field": "CAPTCHA је обавезно поље", + "No such user": "Непостојећи корисник", + "Chinese (Traditional)": "Кинески (Традиционални)", + "adminprefs_modified_source_code_url_label": "УРЛ веза до складишта са Измењеном Изворном Кодом", + "`x` hours": { + "": "`x` сати", + "([^.,0-9]|^)1([^.,0-9]|$)": "`x` сат" + }, + "Lao": "Лаоски", + "Czech": "Чешки", + "Kannada": "Канада (Језик)", + "Polish": "Пољски", + "Cebuano": "Себуано", + "preferences_show_nick_label": "Прикажи надимке на врху: ", + "Report statistics: ": "Извештавај о статистици: ", + "Show more": "Прикажи више", + "Login failed. This may be because two-factor authentication is not turned on for your account.": "Неуспешна пријава! Ово се можда дешава јер двофакторна аутентикација није омогућена на vашем налогу.", + "Wrong answer": "Погрешан одговор", + "Hidden field \"token\" is a required field": "Сакривено \"token\" поље је обавезно", + "English": "Енглески", + "Albanian": "Албански", + "Amharic": "Амхарски", + "Azerbaijani": "Азербејџански", + "Basque": "Баскијски", + "Belarusian": "Белоруски", + "Chinese (Simplified)": "Кинески (Поједностављени)", + "Croatian": "Хрватски", + "Dutch": "Холандски", + "Esperanto": "Есперанто", + "Finnish": "Фински", + "French": "Француски", + "Georgian": "Грузијски", + "Greek": "Грчки", + "Hausa": "Хауса", + "video": "Видео", + "playlist": "Плеј листа", + "movie": "Филм", + "long": "Дуго (> 20 минута)", + "creative_commons": "Creative Commons (Лиценца)", + "live": "Уживо", + "location": "Локација", + "filter": "Филтер", + "next_steps_error_message": "Након чега би требали пробати: ", + "footer_donate_page": "Донирај", + "footer_documentation": "Документација", + "footer_modfied_source_code": "Измењена Изворна Кода", + "preferences_region_label": "Држава порекла садржаја: ", + "preferences_category_misc": "Остала подешавања", + "User ID is a required field": "Кориснички ИД је обавезно поље", + "Password is a required field": "Лозинка је обавезно поље", + "Wrong username or password": "Погрешно корисничко име или лозинка", + "Please sign in using 'Log in with Google'": "Молимо Вас да се пријавите помоћу 'Log in with Google'", + "Password cannot be empty": "Лозинка не може бити празна", + "Password cannot be longer than 55 characters": "Лозинка не може бити дужа од 55 карактера", + "Invidious Private Feed for `x`": "Инвидиоус Приватни Довод за `x`", + "Deleted or invalid channel": "Обрисан или непостојећи канал", + "This channel does not exist.": "Овај канал не постоји.", + "`x` points": { + "": "`x` поена", + "([^.,0-9]|^)1([^.,0-9]|$)": "`x` поен" + }, + "Could not create mix.": "Прављење микса није успело.", + "Empty playlist": "Празна плеј листа", + "Not a playlist.": "Није плеј листа.", + "Playlist does not exist.": "Непостојећа плеј листа.", + "Could not pull trending pages.": "Учитавање 'У току' страница није успело.", + "Hidden field \"challenge\" is a required field": "Сакривено \"challenge\" поље је обавезно", + "Telugu": "Телугу", + "Turkish": "Турски", + "Urdu": "Урду", + "Western Frisian": "Западнофрисијски", + "Xhosa": "Коса (Језик)", + "Yiddish": "Јидиш", + "`x` years": { + "([^.,0-9]|^)1([^.,0-9]|$)": "`x` година", + "": "`x` година" + }, + "`x` weeks": { + "": "`x` седмица", + "([^.,0-9]|^)1([^.,0-9]|$)": "`x` седмица" + }, + "Hawaiian": "Хавајски", + "Hmong": "Хмонг", + "Hungarian": "Мађарски", + "Igbo": "Игбо", + "Javanese": "Јавански", + "Khmer": "Кмерски", + "Kyrgyz": "Киргиски", + "Macedonian": "Македонски", + "Maori": "Маори (Језик)", + "Marathi": "Маратхи", + "Nepali": "Непалски", + "Norwegian Bokmål": "Норвешки Бокмал", + "Nyanja": "Чева", + "Russian": "Руски", + "Scottish Gaelic": "Шкотски Гелски", + "Shona": "Шона", + "Slovak": "Словачки", + "Spanish (Latin America)": "Шпански (Јужна Америка)", + "Sundanese": "Сундски", + "Swahili": "Свахили", + "Tajik": "Таџички", + "`x` days": { + "": "`x` дана", + "([^.,0-9]|^)1([^.,0-9]|$)": "`x` дан" + }, + "`x` minutes": { + "([^.,0-9]|^)1([^.,0-9]|$)": "`x` минут", + "": "`x` минута" + }, + "`x` seconds": { + "([^.,0-9]|^)1([^.,0-9]|$)": "`x` секунда", + "": "`x` секунди" + }, + "Search": "Претрага", + "Rating: ": "Ocena/e: ", + "Default": "Подразумеван/о", + "News": "Вести", + "Download": "Преузми", + "(edited)": "(измењено)", + "`x` marked it with a ❤": "`x` је означио/ла ово са ❤", + "Audio mode": "Аудио мод", + "Videos": "Видео клипови", + "views": "Број прегледа", + "features": "Карактеристике", + "today": "Данас", + "%A %B %-d, %Y": "%A %B %-d, %Y", + "preferences_locale_label": "Језик: ", + "Persian": "Перзијски", + "View `x` comments": { + "": "Прикажи `x` коментара", + "([^.,0-9]|^)1([^.,0-9]|$)": "Прикажи `x` коментар" + }, + "channel": "Канал", + "Haitian Creole": "Хаићански Креолски", + "Armenian": "Јерменски", + "View `x` replies": { + "": "Прикажи `x` одговора", + "([^.,0-9]|^)1([^.,0-9]|$)": "Прикажи `x` одговор" + }, + "next_steps_error_message_go_to_youtube": "Иди на YouTube", + "`x` months": { + "([^.,0-9]|^)1([^.,0-9]|$)": "`x` месец", + "": "`x` месеци" + }, + "Indonesian": "Индонежански", + "preferences_vr_mode_label": "Интерактивни видео клипови у 360 степени: ", + "Switch Invidious Instance": "Промени Invidious инстанцу", + "Portuguese": "Португалски", + "week": "Ове седмице", + "show": "Емисија", + "Fallback comments: ": "Коментари у случају отказивања: ", + "hdr": "Видео Високе Резолуције", + "About": "О програму", + "Kazakh": "Казашки", + "Shared `x`": "Подељено `x`", + "Playlists": "Плеј листе", + "Yoruba": "Јоруба", + "Erroneous challenge": "Погрешан изазов", + "Danish": "Дански", + "Could not get channel info.": "Узимање података о каналу није успело.", + "hd": "HD", + "Slovenian": "Словеначки", + "Load more": "Учитај више", + "German": "Немачки", + "Luxembourgish": "Луксембуршки", + "Mongolian": "Монголски", + "Latvian": "Летонски", + "channel:`x`": "kanal:`x`", + "Southern Sotho": "Јужни Сото", + "Popular": "Популарно", + "Gujarati": "Гуџарати", + "year": "Ове године", + "Irish": "Ирски", + "YouTube comment permalink": "YouTube коментар трајна веза", + "Malagasy": "Малгашки", + "Token is expired, please try again": "Жетон је истекао, молимо вас да покушате поново", + "short": "Кратко (< 4 минуте)", + "Samoan": "Самоански", + "Tamil": "Тамилски", + "Ukrainian": "Украјински", + "permalink": "трајна веза", + "Pashto": "Паштунски", + "Community": "Заједница", + "Sindhi": "Синди", + "Could not fetch comments": "Узимање коментара није успело", + "Bangla": "Бангла/Бенгалски", + "Uzbek": "Узбечки", + "Lithuanian": "Литвански", + "Icelandic": "Исландски", + "Thai": "Тајски", + "month": "Овај месец", + "content_type": "Тип", + "hour": "Последњи сат", + "Spanish": "Шпански", + "date": "Датум отпремања", + "View as playlist": "Погледај као плеј листу", + "relevance": "Релевантност", + "Estonian": "Естонски", + "Sinhala": "Синхалешки", + "Corsican": "Корзикански", + "Filipino": "Филипино", + "Gaming": "Игрице", + "Movies": "Филмови", + "rating": "Оцене", + "Top enabled: ": "Врх омогућен: ", + "Released under the AGPLv3 on Github.": "Избачено под лиценцом AGPLv3 на Github-у.", + "Afrikaans": "Африканс", + "preferences_automatic_instance_redirect_label": "Аутоматско пребацивање на другу инстанцу у случају отказивања (пречи ће назад на редирецт.инвидиоус.ио): ", + "Invalid TFA code": "Неважећа TFA кода", + "Please log in": "Молимо вас да се пријавите", + "English (auto-generated)": "Енглески (аутоматски генерисано)", + "Hindi": "Хинди", + "Italian": "Талијански", + "Malayalam": "Малајалам", + "Punjabi": "Пунџаби", + "Somali": "Сомалијски", + "Vietnamese": "Вијетнамски", + "Welsh": "Велшки", + "Zulu": "Зулу", + "Maltese": "Малтешки", + "Swedish": "Шведски", + "Music": "Музика", + "Download as: ": "Преузми као: ", + "duration": "Трајање", + "sort": "Поредај према", + "subtitles": "Титл/Превод", + "preferences_extend_desc_label": "Аутоматски прикажи цео опис видеа: ", + "Show less": "Прикажи мање", + "Broken? Try another Invidious Instance": "Не функционише исправно? Пробајте другу Invidious инстанцу", + "Family friendly? ": "Погодно за породицу? ", + "Premieres `x`": "Премерe у `x`", + "Bosnian": "Босански", + "Catalan": "Каталонски", + "Japanese": "Јапански", + "Latin": "Латински", + "next_steps_error_message_refresh": "Освежи страницу", + "footer_original_source_code": "Оригинална Изворна Кода", + "Romanian": "Румунски", + "Serbian": "Српски", + "Top": "Врх", + "Video mode": "Видео мод", + "footer_source_code": "Изворна Кода", + "3d": "3D", + "4k": "4K", + "Erroneous CAPTCHA": "Погрешна CAPTCHA", + "`x` ago": "пре `x`", + "Arabic": "Арапски", + "Bulgarian": "Бугарски", + "Galician": "Галицијски", + "Hebrew": "Хебрејски", + "Korean": "Корејски", + "Kurdish": "Курдски", + "Malay": "Малајски" } diff --git a/locales/tr.json b/locales/tr.json index 6e753bfc..cf427666 100644 --- a/locales/tr.json +++ b/locales/tr.json @@ -433,5 +433,34 @@ "adminprefs_modified_source_code_url_label": "Değiştirilmiş kaynak kodları deposunun URL'si", "footer_donate_page": "Bağış yap", "preferences_region_label": "İçerik ülkesi: ", - "preferences_quality_dash_label": "Tercih edilen dash video kalitesi: " + "preferences_quality_dash_label": "Tercih edilen DASH video kalitesi: ", + "preferences_quality_option_hd720": "HD720", + "preferences_quality_dash_option_best": "En iyi", + "preferences_quality_dash_option_worst": "En kötü", + "preferences_quality_dash_option_4320p": "4320p", + "preferences_quality_dash_option_2160p": "2160p", + "preferences_quality_dash_option_480p": "480p", + "preferences_quality_dash_option_360p": "360p", + "preferences_quality_dash_option_240p": "240p", + "preferences_quality_dash_option_144p": "144p", + "invidious": "Invidious", + "none": "yok", + "videoinfo_started_streaming_x_ago": "`x` önce yayına başladı", + "videoinfo_youTube_embed_link": "Göm", + "videoinfo_invidious_embed_link": "Bağlantıyı Göm", + "user_created_playlists": "`x` oluşturulan oynatma listeleri", + "user_saved_playlists": "`x` kaydedilen oynatma listeleri", + "preferences_quality_option_small": "Küçük", + "preferences_quality_dash_option_720p": "720p", + "preferences_quality_option_medium": "Orta", + "preferences_quality_dash_option_1440p": "1440p", + "preferences_quality_dash_option_1080p": "1080p", + "Video unavailable": "Video kullanılamıyor", + "preferences_quality_option_dash": "DASH (uyarlanabilir kalite)", + "preferences_quality_dash_option_auto": "Otomatik", + "purchased": "Satın alınan", + "360": "360°", + "videoinfo_watch_on_youTube": "YouTube'da izle", + "download_subtitles": "Alt yazılar - `x` (.vtt)", + "preferences_save_player_pos_label": "Geçerli video zamanını kaydet: " } diff --git a/locales/zh-CN.json b/locales/zh-CN.json index 8821ccf9..ed5d82ce 100644 --- a/locales/zh-CN.json +++ b/locales/zh-CN.json @@ -433,5 +433,5 @@ "footer_original_source_code": "原始源代码", "footer_donate_page": "捐赠", "preferences_region_label": "内容国家: ", - "preferences_quality_dash_label": "首选 dash 视频分辨率: " + "preferences_quality_dash_label": "首选 DASH 视频分辨率: " } diff --git a/locales/zh-TW.json b/locales/zh-TW.json index b23e439a..aad51069 100644 --- a/locales/zh-TW.json +++ b/locales/zh-TW.json @@ -433,5 +433,34 @@ "adminprefs_modified_source_code_url_label": "修改後的原始碼倉庫 URL", "footer_donate_page": "捐款", "preferences_region_label": "內容國家: ", - "preferences_quality_dash_label": "偏好的 dash 影片品質: " + "preferences_quality_dash_label": "偏好的 DASH 影片品質: ", + "preferences_quality_option_hd720": "HD720", + "preferences_quality_dash_option_worst": "最差", + "preferences_quality_dash_option_4320p": "4320p", + "preferences_quality_dash_option_2160p": "2160p", + "preferences_quality_dash_option_1440p": "1440p", + "preferences_quality_dash_option_1080p": "1080p", + "preferences_quality_dash_option_720p": "720p", + "preferences_quality_dash_option_480p": "480p", + "preferences_quality_dash_option_360p": "360p", + "preferences_quality_dash_option_240p": "240p", + "preferences_quality_dash_option_144p": "144p", + "invidious": "Invidious", + "purchased": "已購買", + "360": "360°", + "none": "無", + "videoinfo_started_streaming_x_ago": "`x` 前開始串流", + "videoinfo_watch_on_youTube": "在 YouTube 上觀看", + "videoinfo_youTube_embed_link": "嵌入", + "videoinfo_invidious_embed_link": "嵌入連結", + "download_subtitles": "字幕 - `x` (.vtt)", + "user_created_playlists": "`x` 已建立的播放清單", + "user_saved_playlists": "`x` 已儲存的播放清單", + "Video unavailable": "影片不可用", + "preferences_quality_option_small": "小", + "preferences_quality_option_dash": "DASH(主動調整品質)", + "preferences_quality_option_medium": "中等", + "preferences_quality_dash_option_auto": "自動", + "preferences_quality_dash_option_best": "最佳", + "preferences_save_player_pos_label": "儲存目前影片時間: " } diff --git a/spec/helpers_spec.cr b/spec/helpers_spec.cr index 002c8bdd..4215b2bd 100644 --- a/spec/helpers_spec.cr +++ b/spec/helpers_spec.cr @@ -77,16 +77,6 @@ describe "Helper" do end end - describe "#produce_comment_reply_continuation" do - it "correctly produces a continuation token for replies to a given comment" do - produce_comment_reply_continuation("cIHQWOoJeag", "UCq6VFHwMzcMXbuKyG7SQYIg", "Ugx1IP_wGVv3WtGWcdV4AaABAg").should eq("EiYSC2NJSFFXT29KZWFnwAEByAEB4AEBogINKP___________wFAABgGMk0aSxIaVWd4MUlQX3dHVnYzV3RHV2NkVjRBYUFCQWciAggAKhhVQ3E2VkZId016Y01YYnVLeUc3U1FZSWcyC2NJSFFXT29KZWFnQAFICg%3D%3D") - - produce_comment_reply_continuation("cIHQWOoJeag", "UCq6VFHwMzcMXbuKyG7SQYIg", "Ugza62y_TlmTu9o2RfF4AaABAg").should eq("EiYSC2NJSFFXT29KZWFnwAEByAEB4AEBogINKP___________wFAABgGMk0aSxIaVWd6YTYyeV9UbG1UdTlvMlJmRjRBYUFCQWciAggAKhhVQ3E2VkZId016Y01YYnVLeUc3U1FZSWcyC2NJSFFXT29KZWFnQAFICg%3D%3D") - - produce_comment_reply_continuation("_cE8xSu6swE", "UC1AZY74-dGVPe6bfxFwwEMg", "UgyBUaRGHB9Jmt1dsUZ4AaABAg").should eq("EiYSC19jRTh4U3U2c3dFwAEByAEB4AEBogINKP___________wFAABgGMk0aSxIaVWd5QlVhUkdIQjlKbXQxZHNVWjRBYUFCQWciAggAKhhVQzFBWlk3NC1kR1ZQZTZiZnhGd3dFTWcyC19jRTh4U3U2c3dFQAFICg%3D%3D") - end - end - describe "#produce_channel_community_continuation" do it "correctly produces a continuation token for a channel community" do produce_channel_community_continuation("UCCj956IF62FbT7Gouszaj9w", "Egljb21tdW5pdHm4").should eq("4qmFsgIsEhhVQ0NqOTU2SUY2MkZiVDdHb3VzemFqOXcaEEVnbGpiMjF0ZFc1cGRIbTQ%3D") @@ -107,15 +97,6 @@ describe "Helper" do end end - describe "#extract_plid" do - it "correctly extracts playlist ID from trending URL" do - extract_plid("/feed/trending?bp=4gIuCggvbS8wNHJsZhIiUExGZ3F1TG5MNTlhbVBud2pLbmNhZUp3MDYzZlU1M3Q0cA%3D%3D").should eq("PLFgquLnL59amPnwjKncaeJw063fU53t4p") - extract_plid("/feed/trending?bp=4gIvCgkvbS8wYnp2bTISIlBMaUN2Vkp6QnVwS2tDaFNnUDdGWFhDclo2aEp4NmtlTm0%3D").should eq("PLiCvVJzBupKkChSgP7FXXCrZ6hJx6keNm") - extract_plid("/feed/trending?bp=4gIuCggvbS8wNWpoZxIiUEwzWlE1Q3BOdWxRbUtPUDNJekdsYWN0V1c4dklYX0hFUA%3D%3D").should eq("PL3ZQ5CpNulQmKOP3IzGlactWW8vIX_HEP") - extract_plid("/feed/trending?bp=4gIuCggvbS8wMnZ4bhIiUEx6akZiYUZ6c21NUnFhdEJnVTdPeGNGTkZhQ2hqTkVERA%3D%3D").should eq("PLzjFbaFzsmMRqatBgU7OxcFNFaChjNEDD") - end - end - describe "#sign_token" do it "correctly signs a given hash" do token = { diff --git a/src/invidious.cr b/src/invidious.cr index 21a12ff2..ade13608 100644 --- a/src/invidious.cr +++ b/src/invidious.cr @@ -408,7 +408,7 @@ define_video_playback_routes() # Users post "/watch_ajax" do |env| - locale = LOCALES[env.get("preferences").as(Preferences).locale]? + locale = env.get("preferences").as(Preferences).locale user = env.get? "user" sid = env.get? "sid" @@ -478,7 +478,7 @@ end # /modify_notifications?receive_all_updates=false&receive_no_updates=false # will "unding" all subscriptions. get "/modify_notifications" do |env| - locale = LOCALES[env.get("preferences").as(Preferences).locale]? + locale = env.get("preferences").as(Preferences).locale user = env.get? "user" sid = env.get? "sid" @@ -551,7 +551,7 @@ get "/modify_notifications" do |env| end post "/subscription_ajax" do |env| - locale = LOCALES[env.get("preferences").as(Preferences).locale]? + locale = env.get("preferences").as(Preferences).locale user = env.get? "user" sid = env.get? "sid" @@ -621,7 +621,7 @@ post "/subscription_ajax" do |env| end get "/subscription_manager" do |env| - locale = LOCALES[env.get("preferences").as(Preferences).locale]? + locale = env.get("preferences").as(Preferences).locale user = env.get? "user" sid = env.get? "sid" @@ -724,7 +724,7 @@ get "/subscription_manager" do |env| end get "/data_control" do |env| - locale = LOCALES[env.get("preferences").as(Preferences).locale]? + locale = env.get("preferences").as(Preferences).locale user = env.get? "user" referer = get_referer(env) @@ -739,7 +739,7 @@ get "/data_control" do |env| end post "/data_control" do |env| - locale = LOCALES[env.get("preferences").as(Preferences).locale]? + locale = env.get("preferences").as(Preferences).locale user = env.get? "user" referer = get_referer(env) @@ -902,7 +902,7 @@ post "/data_control" do |env| end get "/change_password" do |env| - locale = LOCALES[env.get("preferences").as(Preferences).locale]? + locale = env.get("preferences").as(Preferences).locale user = env.get? "user" sid = env.get? "sid" @@ -920,7 +920,7 @@ get "/change_password" do |env| end post "/change_password" do |env| - locale = LOCALES[env.get("preferences").as(Preferences).locale]? + locale = env.get("preferences").as(Preferences).locale user = env.get? "user" sid = env.get? "sid" @@ -976,7 +976,7 @@ post "/change_password" do |env| end get "/delete_account" do |env| - locale = LOCALES[env.get("preferences").as(Preferences).locale]? + locale = env.get("preferences").as(Preferences).locale user = env.get? "user" sid = env.get? "sid" @@ -994,7 +994,7 @@ get "/delete_account" do |env| end post "/delete_account" do |env| - locale = LOCALES[env.get("preferences").as(Preferences).locale]? + locale = env.get("preferences").as(Preferences).locale user = env.get? "user" sid = env.get? "sid" @@ -1028,7 +1028,7 @@ post "/delete_account" do |env| end get "/clear_watch_history" do |env| - locale = LOCALES[env.get("preferences").as(Preferences).locale]? + locale = env.get("preferences").as(Preferences).locale user = env.get? "user" sid = env.get? "sid" @@ -1046,7 +1046,7 @@ get "/clear_watch_history" do |env| end post "/clear_watch_history" do |env| - locale = LOCALES[env.get("preferences").as(Preferences).locale]? + locale = env.get("preferences").as(Preferences).locale user = env.get? "user" sid = env.get? "sid" @@ -1071,7 +1071,7 @@ post "/clear_watch_history" do |env| end get "/authorize_token" do |env| - locale = LOCALES[env.get("preferences").as(Preferences).locale]? + locale = env.get("preferences").as(Preferences).locale user = env.get? "user" sid = env.get? "sid" @@ -1099,7 +1099,7 @@ get "/authorize_token" do |env| end post "/authorize_token" do |env| - locale = LOCALES[env.get("preferences").as(Preferences).locale]? + locale = env.get("preferences").as(Preferences).locale user = env.get? "user" sid = env.get? "sid" @@ -1147,7 +1147,7 @@ post "/authorize_token" do |env| end get "/token_manager" do |env| - locale = LOCALES[env.get("preferences").as(Preferences).locale]? + locale = env.get("preferences").as(Preferences).locale user = env.get? "user" sid = env.get? "sid" @@ -1165,7 +1165,7 @@ get "/token_manager" do |env| end post "/token_ajax" do |env| - locale = LOCALES[env.get("preferences").as(Preferences).locale]? + locale = env.get("preferences").as(Preferences).locale user = env.get? "user" sid = env.get? "sid" @@ -1225,7 +1225,7 @@ end {"/channel/:ucid/live", "/user/:user/live", "/c/:user/live"}.each do |route| get route do |env| - locale = LOCALES[env.get("preferences").as(Preferences).locale]? + locale = env.get("preferences").as(Preferences).locale # Appears to be a bug in routing, having several routes configured # as `/a/:a`, `/b/:a`, `/c/:a` results in 404 @@ -1347,7 +1347,7 @@ error 404 do |env| end error 500 do |env, ex| - locale = LOCALES[env.get("preferences").as(Preferences).locale]? + locale = env.get("preferences").as(Preferences).locale error_template(500, ex) end diff --git a/src/invidious/comments.cr b/src/invidious/comments.cr index ffdce000..12a80bc4 100644 --- a/src/invidious/comments.cr +++ b/src/invidious/comments.cr @@ -60,8 +60,6 @@ def fetch_youtube_comments(id, cursor, format, locale, thin_mode, region, sort_b case cursor when nil, "" ctoken = produce_comment_continuation(id, cursor: "", sort_by: sort_by) - # when .starts_with? "Ug" - # ctoken = produce_comment_reply_continuation(id, video.ucid, cursor) when .starts_with? "ADSJ" ctoken = produce_comment_continuation(id, cursor: cursor, sort_by: sort_by) else @@ -575,7 +573,9 @@ def content_to_comment_html(content) url = "/watch?v=#{url.request_target.lstrip('/')}" elsif url.host.nil? || url.host.not_nil!.ends_with?("youtube.com") if url.path == "/redirect" - url = HTTP::Params.parse(url.query.not_nil!)["q"] + # Sometimes, links can be corrupted (why?) so make sure to fallback + # nicely. See https://github.com/iv-org/invidious/issues/2682 + url = HTTP::Params.parse(url.query.not_nil!)["q"]? || "" else url = url.request_target end @@ -645,38 +645,3 @@ def produce_comment_continuation(video_id, cursor = "", sort_by = "top") return continuation end - -def produce_comment_reply_continuation(video_id, ucid, comment_id) - object = { - "2:embedded" => { - "2:string" => video_id, - "24:varint" => 1_i64, - "25:varint" => 1_i64, - "28:varint" => 1_i64, - "36:embedded" => { - "5:varint" => -1_i64, - "8:varint" => 0_i64, - }, - }, - "3:varint" => 6_i64, - "6:embedded" => { - "3:embedded" => { - "2:string" => comment_id, - "4:embedded" => { - "1:varint" => 0_i64, - }, - "5:string" => ucid, - "6:string" => video_id, - "8:varint" => 1_i64, - "9:varint" => 10_i64, - }, - }, - } - - continuation = object.try { |i| Protodec::Any.cast_json(i) } - .try { |i| Protodec::Any.from_json(i) } - .try { |i| Base64.urlsafe_encode(i) } - .try { |i| URI.encode_www_form(i) } - - return continuation -end diff --git a/src/invidious/config.cr b/src/invidious/config.cr index 578e31fd..c4a8bf83 100644 --- a/src/invidious/config.cr +++ b/src/invidious/config.cr @@ -42,6 +42,7 @@ struct ConfigPreferences property volume : Int32 = 100 property vr_mode : Bool = true property show_nick : Bool = true + property save_player_pos : Bool = false def to_tuple {% begin %} diff --git a/src/invidious/helpers/errors.cr b/src/invidious/helpers/errors.cr index e5c77fbc..d10762c5 100644 --- a/src/invidious/helpers/errors.cr +++ b/src/invidious/helpers/errors.cr @@ -22,7 +22,7 @@ def github_details(summary : String, content : String) return HTML.escape(details) end -def error_template_helper(env : HTTP::Server::Context, locale : Hash(String, JSON::Any) | Nil, status_code : Int32, exception : Exception) +def error_template_helper(env : HTTP::Server::Context, locale : String?, status_code : Int32, exception : Exception) if exception.is_a?(InfoException) return error_template_helper(env, locale, status_code, exception.message || "") end @@ -46,7 +46,7 @@ def error_template_helper(env : HTTP::Server::Context, locale : Hash(String, JSO return templated "error" end -def error_template_helper(env : HTTP::Server::Context, locale : Hash(String, JSON::Any) | Nil, status_code : Int32, message : String) +def error_template_helper(env : HTTP::Server::Context, locale : String?, status_code : Int32, message : String) env.response.content_type = "text/html" env.response.status_code = status_code error_message = translate(locale, message) @@ -58,7 +58,7 @@ macro error_atom(*args) error_atom_helper(env, locale, {{*args}}) end -def error_atom_helper(env : HTTP::Server::Context, locale : Hash(String, JSON::Any) | Nil, status_code : Int32, exception : Exception) +def error_atom_helper(env : HTTP::Server::Context, locale : String?, status_code : Int32, exception : Exception) if exception.is_a?(InfoException) return error_atom_helper(env, locale, status_code, exception.message || "") end @@ -67,7 +67,7 @@ def error_atom_helper(env : HTTP::Server::Context, locale : Hash(String, JSON::A return "<error>#{exception.inspect_with_backtrace}</error>" end -def error_atom_helper(env : HTTP::Server::Context, locale : Hash(String, JSON::Any) | Nil, status_code : Int32, message : String) +def error_atom_helper(env : HTTP::Server::Context, locale : String?, status_code : Int32, message : String) env.response.content_type = "application/atom+xml" env.response.status_code = status_code return "<error>#{message}</error>" @@ -77,7 +77,7 @@ macro error_json(*args) error_json_helper(env, locale, {{*args}}) end -def error_json_helper(env : HTTP::Server::Context, locale : Hash(String, JSON::Any) | Nil, status_code : Int32, exception : Exception, additional_fields : Hash(String, Object) | Nil) +def error_json_helper(env : HTTP::Server::Context, locale : String?, status_code : Int32, exception : Exception, additional_fields : Hash(String, Object) | Nil) if exception.is_a?(InfoException) return error_json_helper(env, locale, status_code, exception.message || "", additional_fields) end @@ -90,11 +90,11 @@ def error_json_helper(env : HTTP::Server::Context, locale : Hash(String, JSON::A return error_message.to_json end -def error_json_helper(env : HTTP::Server::Context, locale : Hash(String, JSON::Any) | Nil, status_code : Int32, exception : Exception) +def error_json_helper(env : HTTP::Server::Context, locale : String?, status_code : Int32, exception : Exception) return error_json_helper(env, locale, status_code, exception, nil) end -def error_json_helper(env : HTTP::Server::Context, locale : Hash(String, JSON::Any) | Nil, status_code : Int32, message : String, additional_fields : Hash(String, Object) | Nil) +def error_json_helper(env : HTTP::Server::Context, locale : String?, status_code : Int32, message : String, additional_fields : Hash(String, Object) | Nil) env.response.content_type = "application/json" env.response.status_code = status_code error_message = {"error" => message} @@ -104,11 +104,11 @@ def error_json_helper(env : HTTP::Server::Context, locale : Hash(String, JSON::A return error_message.to_json end -def error_json_helper(env : HTTP::Server::Context, locale : Hash(String, JSON::Any) | Nil, status_code : Int32, message : String) +def error_json_helper(env : HTTP::Server::Context, locale : String?, status_code : Int32, message : String) error_json_helper(env, locale, status_code, message, nil) end -def error_redirect_helper(env : HTTP::Server::Context, locale : Hash(String, JSON::Any) | Nil) +def error_redirect_helper(env : HTTP::Server::Context, locale : String?) request_path = env.request.path if request_path.starts_with?("/search") || request_path.starts_with?("/watch") || diff --git a/src/invidious/helpers/helpers.cr b/src/invidious/helpers/helpers.cr index c3b356a9..96a78eb9 100644 --- a/src/invidious/helpers/helpers.cr +++ b/src/invidious/helpers/helpers.cr @@ -190,7 +190,7 @@ def create_notification_stream(env, topics, connection_channel) connection = Channel(PQ::Notification).new(8) connection_channel.send({true, connection}) - locale = LOCALES[env.get("preferences").as(Preferences).locale]? + locale = env.get("preferences").as(Preferences).locale since = env.params.query["since"]?.try &.to_i? id = 0 diff --git a/src/invidious/helpers/i18n.cr b/src/invidious/helpers/i18n.cr index 9e42fad0..fd3ddbad 100644 --- a/src/invidious/helpers/i18n.cr +++ b/src/invidious/helpers/i18n.cr @@ -1,46 +1,47 @@ # "bn_BD" => load_locale("bn_BD"), # Bengali (Bangladesh) [Incomplete] # "eu" => load_locale("eu"), # Basque [Incomplete] -# "si" => load_locale("si"), # Sinhala [Incomplete] # "sk" => load_locale("sk"), # Slovak [Incomplete] -# "sr" => load_locale("sr"), # Serbian [Incomplete] -# "sr_Cyrl" => load_locale("sr_Cyrl"), # Serbian (cyrillic) [Incomplete] -LOCALES = { - "ar" => load_locale("ar"), # Arabic - "cs" => load_locale("cs"), # Czech - "da" => load_locale("da"), # Danish - "de" => load_locale("de"), # German - "el" => load_locale("el"), # Greek - "en-US" => load_locale("en-US"), # English (US) - "eo" => load_locale("eo"), # Esperanto - "es" => load_locale("es"), # Spanish - "fa" => load_locale("fa"), # Persian - "fi" => load_locale("fi"), # Finnish - "fr" => load_locale("fr"), # French - "he" => load_locale("he"), # Hebrew - "hr" => load_locale("hr"), # Croatian - "hu-HU" => load_locale("hu-HU"), # Hungarian - "id" => load_locale("id"), # Indonesian - "is" => load_locale("is"), # Icelandic - "it" => load_locale("it"), # Italian - "ja" => load_locale("ja"), # Japanese - "ko" => load_locale("ko"), # Korean - "lt" => load_locale("lt"), # Lithuanian - "nb-NO" => load_locale("nb-NO"), # Norwegian Bokmål - "nl" => load_locale("nl"), # Dutch - "pl" => load_locale("pl"), # Polish - "pt" => load_locale("pt"), # Portuguese - "pt-BR" => load_locale("pt-BR"), # Portuguese (Brazil) - "pt-PT" => load_locale("pt-PT"), # Portuguese (Portugal) - "ro" => load_locale("ro"), # Romanian - "ru" => load_locale("ru"), # Russian - "sv-SE" => load_locale("sv-SE"), # Swedish - "tr" => load_locale("tr"), # Turkish - "uk" => load_locale("uk"), # Ukrainian - "vi" => load_locale("vi"), # Vietnamese - "zh-CN" => load_locale("zh-CN"), # Chinese (Simplified) - "zh-TW" => load_locale("zh-TW"), # Chinese (Traditional) +LOCALES_LIST = { + "ar" => "العربية", # Arabic + "cs" => "Čeština", # Czech + "da" => "Dansk", # Danish + "de" => "Deutsch", # German + "el" => "Ελληνικά", # Greek + "en-US" => "English", # English + "eo" => "Esperanto", # Esperanto + "es" => "Español", # Spanish + "fa" => "فارسی", # Persian + "fi" => "Suomi", # Finnish + "fr" => "Français", # French + "he" => "עברית", # Hebrew + "hr" => "Hrvatski", # Croatian + "hu-HU" => "Magyar Nyelv", # Hungarian + "id" => "Bahasa Indonesia", # Indonesian + "is" => "Íslenska", # Icelandic + "it" => "Italiano", # Italian + "ja" => "日本語", # Japanese + "ko" => "한국어", # Korean + "lt" => "Lietuvių", # Lithuanian + "nb-NO" => "Norsk bokmål", # Norwegian Bokmål + "nl" => "Nederlands", # Dutch + "pl" => "Polski", # Polish + "pt" => "Português", # Portuguese + "pt-BR" => "Português Brasileiro", # Portuguese (Brazil) + "pt-PT" => "Português de Portugal", # Portuguese (Portugal) + "ro" => "Română", # Romanian + "ru" => "русский", # Russian + "sr" => "srpski (latinica)", # Serbian (Latin) + "sr_Cyrl" => "српски (ћирилица)", # Serbian (Cyrillic) + "sv-SE" => "Svenska", # Swedish + "tr" => "Türkçe", # Turkish + "uk" => "Українська", # Ukrainian + "vi" => "Tiếng Việt", # Vietnamese + "zh-CN" => "汉语", # Chinese (Simplified) + "zh-TW" => "漢語", # Chinese (Traditional) } +LOCALES = load_all_locales() + CONTENT_REGIONS = { "AE", "AR", "AT", "AU", "AZ", "BA", "BD", "BE", "BG", "BH", "BO", "BR", "BY", "CA", "CH", "CL", "CO", "CR", "CY", "CZ", "DE", "DK", "DO", "DZ", "EC", "EE", @@ -53,35 +54,50 @@ CONTENT_REGIONS = { "YE", "ZA", "ZW", } -def load_locale(name) - return JSON.parse(File.read("locales/#{name}.json")).as_h +def load_all_locales + locales = {} of String => Hash(String, JSON::Any) + + LOCALES_LIST.each_key do |name| + locales[name] = JSON.parse(File.read("locales/#{name}.json")).as_h + end + + return locales end -def translate(locale : Hash(String, JSON::Any) | Nil, translation : String, text : String | Nil = nil) - # if locale && !locale[translation]? - # puts "Could not find translation for #{translation.dump}" - # end +def translate(locale : String?, key : String, text : String | Nil = nil) : String + # Log a warning if "key" doesn't exist in en-US locale and return + # that key as the text, so this is more or less transparent to the user. + if !LOCALES["en-US"].has_key?(key) + LOGGER.warn("i18n: Missing translation key \"#{key}\"") + return key + end + + # Default to english, whenever the locale doesn't exist, + # or the key requested has not been translated + if locale && LOCALES.has_key?(locale) && LOCALES[locale].has_key?(key) + raw_data = LOCALES[locale][key] + else + raw_data = LOCALES["en-US"][key] + end - if locale && locale[translation]? - case locale[translation] - when .as_h? - match_length = 0 + case raw_data + when .as_h? + # Init + translation = "" + match_length = 0 - locale[translation].as_h.each do |key, value| - if md = text.try &.match(/#{key}/) - if md[0].size >= match_length - translation = value.as_s - match_length = md[0].size - end + raw_data.as_h.each do |key, value| + if md = text.try &.match(/#{key}/) + if md[0].size >= match_length + translation = value.as_s + match_length = md[0].size end end - when .as_s? - if !locale[translation].as_s.empty? - translation = locale[translation].as_s - end - else - raise "Invalid translation #{translation}" end + when .as_s? + translation = raw_data.as_s + else + raise "Invalid translation \"#{raw_data}\"" end if text @@ -91,7 +107,7 @@ def translate(locale : Hash(String, JSON::Any) | Nil, translation : String, text return translation end -def translate_bool(locale : Hash(String, JSON::Any) | Nil, translation : Bool) +def translate_bool(locale : String?, translation : Bool) case translation when true return translate(locale, "Yes") diff --git a/src/invidious/helpers/serialized_yt_data.cr b/src/invidious/helpers/serialized_yt_data.cr index f92b7b89..bfbc237c 100644 --- a/src/invidious/helpers/serialized_yt_data.cr +++ b/src/invidious/helpers/serialized_yt_data.cr @@ -64,7 +64,7 @@ struct SearchVideo end end - def to_json(locale : Hash(String, JSON::Any) | Nil, json : JSON::Builder) + def to_json(locale : String?, json : JSON::Builder) json.object do json.field "type", "video" json.field "title", self.title @@ -96,7 +96,7 @@ struct SearchVideo end # TODO: remove the locale and follow the crystal convention - def to_json(locale : Hash(String, JSON::Any) | Nil, _json : Nil) + def to_json(locale : String?, _json : Nil) JSON.build do |json| to_json(locale, json) end @@ -130,7 +130,7 @@ struct SearchPlaylist property videos : Array(SearchPlaylistVideo) property thumbnail : String? - def to_json(locale : Hash(String, JSON::Any) | Nil, json : JSON::Builder) + def to_json(locale : String?, json : JSON::Builder) json.object do json.field "type", "playlist" json.field "title", self.title @@ -161,7 +161,7 @@ struct SearchPlaylist end # TODO: remove the locale and follow the crystal convention - def to_json(locale : Hash(String, JSON::Any) | Nil, _json : Nil) + def to_json(locale : String?, _json : Nil) JSON.build do |json| to_json(locale, json) end @@ -183,7 +183,7 @@ struct SearchChannel property description_html : String property auto_generated : Bool - def to_json(locale : Hash(String, JSON::Any) | Nil, json : JSON::Builder) + def to_json(locale : String?, json : JSON::Builder) json.object do json.field "type", "channel" json.field "author", self.author @@ -214,7 +214,7 @@ struct SearchChannel end # TODO: remove the locale and follow the crystal convention - def to_json(locale : Hash(String, JSON::Any) | Nil, _json : Nil) + def to_json(locale : String?, _json : Nil) JSON.build do |json| to_json(locale, json) end @@ -234,7 +234,7 @@ class Category property description_html : String property badges : Array(Tuple(String, String))? - def to_json(locale : Hash(String, JSON::Any) | Nil, json : JSON::Builder) + def to_json(locale : String?, json : JSON::Builder) json.object do json.field "type", "category" json.field "title", self.title @@ -249,7 +249,7 @@ class Category end # TODO: remove the locale and follow the crystal convention - def to_json(locale : Hash(String, JSON::Any) | Nil, _json : Nil) + def to_json(locale : String?, _json : Nil) JSON.build do |json| to_json(locale, json) end diff --git a/src/invidious/helpers/utils.cr b/src/invidious/helpers/utils.cr index 603b4e1f..7bbbcb92 100644 --- a/src/invidious/helpers/utils.cr +++ b/src/invidious/helpers/utils.cr @@ -21,10 +21,17 @@ def elapsed_text(elapsed) end def decode_length_seconds(string) - length_seconds = string.gsub(/[^0-9:]/, "").split(":").map &.to_i + length_seconds = string.gsub(/[^0-9:]/, "") + return 0_i32 if length_seconds.empty? + + length_seconds = length_seconds.split(":").map { |x| x.to_i? || 0 } length_seconds = [0] * (3 - length_seconds.size) + length_seconds - length_seconds = Time::Span.new hours: length_seconds[0], minutes: length_seconds[1], seconds: length_seconds[2] - length_seconds = length_seconds.total_seconds.to_i + + length_seconds = Time::Span.new( + hours: length_seconds[0], + minutes: length_seconds[1], + seconds: length_seconds[2] + ).total_seconds.to_i32 return length_seconds end diff --git a/src/invidious/routes/api/v1/authenticated.cr b/src/invidious/routes/api/v1/authenticated.cr index cdd9e2f6..aaf728ff 100644 --- a/src/invidious/routes/api/v1/authenticated.cr +++ b/src/invidious/routes/api/v1/authenticated.cr @@ -36,7 +36,7 @@ module Invidious::Routes::API::V1::Authenticated env.response.content_type = "application/json" user = env.get("user").as(User) - locale = LOCALES[env.get("preferences").as(Preferences).locale]? + locale = env.get("preferences").as(Preferences).locale max_results = env.params.query["max_results"]?.try &.to_i? max_results ||= user.preferences.max_results @@ -122,7 +122,7 @@ module Invidious::Routes::API::V1::Authenticated end def self.list_playlists(env) - locale = LOCALES[env.get("preferences").as(Preferences).locale]? + locale = env.get("preferences").as(Preferences).locale env.response.content_type = "application/json" user = env.get("user").as(User) @@ -141,7 +141,7 @@ module Invidious::Routes::API::V1::Authenticated def self.create_playlist(env) env.response.content_type = "application/json" user = env.get("user").as(User) - locale = LOCALES[env.get("preferences").as(Preferences).locale]? + locale = env.get("preferences").as(Preferences).locale title = env.params.json["title"]?.try &.as(String).delete("<>").byte_slice(0, 150) if !title @@ -167,7 +167,7 @@ module Invidious::Routes::API::V1::Authenticated end def self.update_playlist_attribute(env) - locale = LOCALES[env.get("preferences").as(Preferences).locale]? + locale = env.get("preferences").as(Preferences).locale env.response.content_type = "application/json" user = env.get("user").as(User) @@ -200,7 +200,7 @@ module Invidious::Routes::API::V1::Authenticated end def self.delete_playlist(env) - locale = LOCALES[env.get("preferences").as(Preferences).locale]? + locale = env.get("preferences").as(Preferences).locale env.response.content_type = "application/json" user = env.get("user").as(User) @@ -223,7 +223,7 @@ module Invidious::Routes::API::V1::Authenticated end def self.insert_video_into_playlist(env) - locale = LOCALES[env.get("preferences").as(Preferences).locale]? + locale = env.get("preferences").as(Preferences).locale env.response.content_type = "application/json" user = env.get("user").as(User) @@ -281,7 +281,7 @@ module Invidious::Routes::API::V1::Authenticated end def self.delete_video_in_playlist(env) - locale = LOCALES[env.get("preferences").as(Preferences).locale]? + locale = env.get("preferences").as(Preferences).locale env.response.content_type = "application/json" user = env.get("user").as(User) @@ -334,7 +334,7 @@ module Invidious::Routes::API::V1::Authenticated def self.register_token(env) user = env.get("user").as(User) - locale = LOCALES[env.get("preferences").as(Preferences).locale]? + locale = env.get("preferences").as(Preferences).locale case env.request.headers["Content-Type"]? when "application/x-www-form-urlencoded" @@ -396,7 +396,7 @@ module Invidious::Routes::API::V1::Authenticated end def self.unregister_token(env) - locale = LOCALES[env.get("preferences").as(Preferences).locale]? + locale = env.get("preferences").as(Preferences).locale env.response.content_type = "application/json" user = env.get("user").as(User) scopes = env.get("scopes").as(Array(String)) diff --git a/src/invidious/routes/api/v1/channels.cr b/src/invidious/routes/api/v1/channels.cr index da39661c..8b6df3fd 100644 --- a/src/invidious/routes/api/v1/channels.cr +++ b/src/invidious/routes/api/v1/channels.cr @@ -1,6 +1,6 @@ module Invidious::Routes::API::V1::Channels def self.home(env) - locale = LOCALES[env.get("preferences").as(Preferences).locale]? + locale = env.get("preferences").as(Preferences).locale env.response.content_type = "application/json" @@ -124,7 +124,7 @@ module Invidious::Routes::API::V1::Channels end def self.latest(env) - locale = LOCALES[env.get("preferences").as(Preferences).locale]? + locale = env.get("preferences").as(Preferences).locale env.response.content_type = "application/json" @@ -146,7 +146,7 @@ module Invidious::Routes::API::V1::Channels end def self.videos(env) - locale = LOCALES[env.get("preferences").as(Preferences).locale]? + locale = env.get("preferences").as(Preferences).locale env.response.content_type = "application/json" @@ -182,7 +182,7 @@ module Invidious::Routes::API::V1::Channels end def self.playlists(env) - locale = LOCALES[env.get("preferences").as(Preferences).locale]? + locale = env.get("preferences").as(Preferences).locale env.response.content_type = "application/json" @@ -219,7 +219,7 @@ module Invidious::Routes::API::V1::Channels end def self.community(env) - locale = LOCALES[env.get("preferences").as(Preferences).locale]? + locale = env.get("preferences").as(Preferences).locale env.response.content_type = "application/json" @@ -242,7 +242,7 @@ module Invidious::Routes::API::V1::Channels end def self.search(env) - locale = LOCALES[env.get("preferences").as(Preferences).locale]? + locale = env.get("preferences").as(Preferences).locale env.response.content_type = "application/json" diff --git a/src/invidious/routes/api/v1/feeds.cr b/src/invidious/routes/api/v1/feeds.cr index bb8f661b..41865f34 100644 --- a/src/invidious/routes/api/v1/feeds.cr +++ b/src/invidious/routes/api/v1/feeds.cr @@ -1,6 +1,6 @@ module Invidious::Routes::API::V1::Feeds def self.trending(env) - locale = LOCALES[env.get("preferences").as(Preferences).locale]? + locale = env.get("preferences").as(Preferences).locale env.response.content_type = "application/json" @@ -25,7 +25,7 @@ module Invidious::Routes::API::V1::Feeds end def self.popular(env) - locale = LOCALES[env.get("preferences").as(Preferences).locale]? + locale = env.get("preferences").as(Preferences).locale env.response.content_type = "application/json" diff --git a/src/invidious/routes/api/v1/misc.cr b/src/invidious/routes/api/v1/misc.cr index 80b59fd5..1621c9ef 100644 --- a/src/invidious/routes/api/v1/misc.cr +++ b/src/invidious/routes/api/v1/misc.cr @@ -1,7 +1,7 @@ module Invidious::Routes::API::V1::Misc # Stats API endpoint for Invidious def self.stats(env) - locale = LOCALES[env.get("preferences").as(Preferences).locale]? + locale = env.get("preferences").as(Preferences).locale env.response.content_type = "application/json" if !CONFIG.statistics_enabled @@ -15,7 +15,7 @@ module Invidious::Routes::API::V1::Misc # user playlists and Invidious playlists. This means that we can't # reasonably split them yet. This should be addressed in APIv2 def self.get_playlist(env) - locale = LOCALES[env.get("preferences").as(Preferences).locale]? + locale = env.get("preferences").as(Preferences).locale env.response.content_type = "application/json" plid = env.params.url["plid"] @@ -84,7 +84,7 @@ module Invidious::Routes::API::V1::Misc end def self.mixes(env) - locale = LOCALES[env.get("preferences").as(Preferences).locale]? + locale = env.get("preferences").as(Preferences).locale env.response.content_type = "application/json" diff --git a/src/invidious/routes/api/v1/search.cr b/src/invidious/routes/api/v1/search.cr index 7234dcdd..a3b6c795 100644 --- a/src/invidious/routes/api/v1/search.cr +++ b/src/invidious/routes/api/v1/search.cr @@ -1,6 +1,6 @@ module Invidious::Routes::API::V1::Search def self.search(env) - locale = LOCALES[env.get("preferences").as(Preferences).locale]? + locale = env.get("preferences").as(Preferences).locale region = env.params.query["region"]? env.response.content_type = "application/json" @@ -43,7 +43,7 @@ module Invidious::Routes::API::V1::Search end def self.search_suggestions(env) - locale = LOCALES[env.get("preferences").as(Preferences).locale]? + locale = env.get("preferences").as(Preferences).locale region = env.params.query["region"]? env.response.content_type = "application/json" diff --git a/src/invidious/routes/api/v1/videos.cr b/src/invidious/routes/api/v1/videos.cr index 1edee29c..4c7179ce 100644 --- a/src/invidious/routes/api/v1/videos.cr +++ b/src/invidious/routes/api/v1/videos.cr @@ -1,6 +1,6 @@ module Invidious::Routes::API::V1::Videos def self.videos(env) - locale = LOCALES[env.get("preferences").as(Preferences).locale]? + locale = env.get("preferences").as(Preferences).locale env.response.content_type = "application/json" @@ -20,7 +20,7 @@ module Invidious::Routes::API::V1::Videos end def self.captions(env) - locale = LOCALES[env.get("preferences").as(Preferences).locale]? + locale = env.get("preferences").as(Preferences).locale env.response.content_type = "application/json" @@ -149,7 +149,7 @@ module Invidious::Routes::API::V1::Videos # thumbnails for individual scenes in a video. # See https://support.jwplayer.com/articles/how-to-add-preview-thumbnails def self.storyboards(env) - locale = LOCALES[env.get("preferences").as(Preferences).locale]? + locale = env.get("preferences").as(Preferences).locale env.response.content_type = "application/json" @@ -223,7 +223,7 @@ module Invidious::Routes::API::V1::Videos end def self.annotations(env) - locale = LOCALES[env.get("preferences").as(Preferences).locale]? + locale = env.get("preferences").as(Preferences).locale env.response.content_type = "text/xml" @@ -293,7 +293,7 @@ module Invidious::Routes::API::V1::Videos end def self.comments(env) - locale = LOCALES[env.get("preferences").as(Preferences).locale]? + locale = env.get("preferences").as(Preferences).locale region = env.params.query["region"]? env.response.content_type = "application/json" diff --git a/src/invidious/routes/channels.cr b/src/invidious/routes/channels.cr index 29748cd0..6cb1e1f7 100644 --- a/src/invidious/routes/channels.cr +++ b/src/invidious/routes/channels.cr @@ -104,7 +104,7 @@ module Invidious::Routes::Channels # Redirects brand url channels to a normal /channel/:ucid route def self.brand_redirect(env) - locale = LOCALES[env.get("preferences").as(Preferences).locale]? + locale = env.get("preferences").as(Preferences).locale # /attribution_link endpoint needs both the `a` and `u` parameter # and in order to avoid detection from YouTube we should only send the required ones @@ -148,7 +148,7 @@ module Invidious::Routes::Channels end private def self.fetch_basic_information(env) - locale = LOCALES[env.get("preferences").as(Preferences).locale]? + locale = env.get("preferences").as(Preferences).locale user = env.get? "user" if user diff --git a/src/invidious/routes/embed.cr b/src/invidious/routes/embed.cr index ffbf8c14..049ee344 100644 --- a/src/invidious/routes/embed.cr +++ b/src/invidious/routes/embed.cr @@ -2,7 +2,7 @@ module Invidious::Routes::Embed def self.redirect(env) - locale = LOCALES[env.get("preferences").as(Preferences).locale]? + locale = env.get("preferences").as(Preferences).locale if plid = env.params.query["list"]?.try &.gsub(/[^a-zA-Z0-9_-]/, "") begin @@ -26,7 +26,7 @@ module Invidious::Routes::Embed end def self.show(env) - locale = LOCALES[env.get("preferences").as(Preferences).locale]? + locale = env.get("preferences").as(Preferences).locale id = env.params.url["id"] plid = env.params.query["list"]?.try &.gsub(/[^a-zA-Z0-9_-]/, "") diff --git a/src/invidious/routes/feeds.cr b/src/invidious/routes/feeds.cr index f4a8467b..9650bcf4 100644 --- a/src/invidious/routes/feeds.cr +++ b/src/invidious/routes/feeds.cr @@ -6,7 +6,7 @@ module Invidious::Routes::Feeds end def self.playlists(env) - locale = LOCALES[env.get("preferences").as(Preferences).locale]? + locale = env.get("preferences").as(Preferences).locale user = env.get? "user" referer = get_referer(env) @@ -31,7 +31,7 @@ module Invidious::Routes::Feeds end def self.popular(env) - locale = LOCALES[env.get("preferences").as(Preferences).locale]? + locale = env.get("preferences").as(Preferences).locale if CONFIG.popular_enabled templated "feeds/popular" @@ -42,7 +42,7 @@ module Invidious::Routes::Feeds end def self.trending(env) - locale = LOCALES[env.get("preferences").as(Preferences).locale]? + locale = env.get("preferences").as(Preferences).locale trending_type = env.params.query["type"]? trending_type ||= "Default" @@ -60,7 +60,7 @@ module Invidious::Routes::Feeds end def self.subscriptions(env) - locale = LOCALES[env.get("preferences").as(Preferences).locale]? + locale = env.get("preferences").as(Preferences).locale user = env.get? "user" sid = env.get? "sid" @@ -108,7 +108,7 @@ module Invidious::Routes::Feeds end def self.history(env) - locale = LOCALES[env.get("preferences").as(Preferences).locale]? + locale = env.get("preferences").as(Preferences).locale user = env.get? "user" referer = get_referer(env) @@ -137,7 +137,7 @@ module Invidious::Routes::Feeds # RSS feeds def self.rss_channel(env) - locale = LOCALES[env.get("preferences").as(Preferences).locale]? + locale = env.get("preferences").as(Preferences).locale env.response.headers["Content-Type"] = "application/atom+xml" env.response.content_type = "application/atom+xml" @@ -209,7 +209,7 @@ module Invidious::Routes::Feeds end def self.rss_private(env) - locale = LOCALES[env.get("preferences").as(Preferences).locale]? + locale = env.get("preferences").as(Preferences).locale env.response.headers["Content-Type"] = "application/atom+xml" env.response.content_type = "application/atom+xml" @@ -253,7 +253,7 @@ module Invidious::Routes::Feeds end def self.rss_playlist(env) - locale = LOCALES[env.get("preferences").as(Preferences).locale]? + locale = env.get("preferences").as(Preferences).locale env.response.headers["Content-Type"] = "application/atom+xml" env.response.content_type = "application/atom+xml" @@ -374,7 +374,7 @@ module Invidious::Routes::Feeds end def self.push_notifications_post(env) - locale = LOCALES[env.get("preferences").as(Preferences).locale]? + locale = env.get("preferences").as(Preferences).locale token = env.params.url["token"] body = env.request.body.not_nil!.gets_to_end diff --git a/src/invidious/routes/login.cr b/src/invidious/routes/login.cr index 562d88e5..2a50561d 100644 --- a/src/invidious/routes/login.cr +++ b/src/invidious/routes/login.cr @@ -2,7 +2,7 @@ module Invidious::Routes::Login def self.login_page(env) - locale = LOCALES[env.get("preferences").as(Preferences).locale]? + locale = env.get("preferences").as(Preferences).locale user = env.get? "user" @@ -31,7 +31,7 @@ module Invidious::Routes::Login end def self.login(env) - locale = LOCALES[env.get("preferences").as(Preferences).locale]? + locale = env.get("preferences").as(Preferences).locale referer = get_referer(env, "/feed/subscriptions") @@ -491,7 +491,7 @@ module Invidious::Routes::Login end def self.signout(env) - locale = LOCALES[env.get("preferences").as(Preferences).locale]? + locale = env.get("preferences").as(Preferences).locale user = env.get? "user" sid = env.get? "sid" diff --git a/src/invidious/routes/misc.cr b/src/invidious/routes/misc.cr index 3ea4c272..d6bd9571 100644 --- a/src/invidious/routes/misc.cr +++ b/src/invidious/routes/misc.cr @@ -3,7 +3,7 @@ module Invidious::Routes::Misc def self.home(env) preferences = env.get("preferences").as(Preferences) - locale = LOCALES[preferences.locale]? + locale = preferences.locale user = env.get? "user" case preferences.default_home @@ -29,12 +29,12 @@ module Invidious::Routes::Misc end def self.privacy(env) - locale = LOCALES[env.get("preferences").as(Preferences).locale]? + locale = env.get("preferences").as(Preferences).locale templated "privacy" end def self.licenses(env) - locale = LOCALES[env.get("preferences").as(Preferences).locale]? + locale = env.get("preferences").as(Preferences).locale rendered "licenses" end diff --git a/src/invidious/routes/playlists.cr b/src/invidious/routes/playlists.cr index 21126d7e..7b7bd03f 100644 --- a/src/invidious/routes/playlists.cr +++ b/src/invidious/routes/playlists.cr @@ -2,7 +2,7 @@ module Invidious::Routes::Playlists def self.new(env) - locale = LOCALES[env.get("preferences").as(Preferences).locale]? + locale = env.get("preferences").as(Preferences).locale user = env.get? "user" sid = env.get? "sid" @@ -18,7 +18,7 @@ module Invidious::Routes::Playlists end def self.create(env) - locale = LOCALES[env.get("preferences").as(Preferences).locale]? + locale = env.get("preferences").as(Preferences).locale user = env.get? "user" sid = env.get? "sid" @@ -56,7 +56,7 @@ module Invidious::Routes::Playlists end def self.subscribe(env) - locale = LOCALES[env.get("preferences").as(Preferences).locale]? + locale = env.get("preferences").as(Preferences).locale user = env.get? "user" referer = get_referer(env) @@ -73,7 +73,7 @@ module Invidious::Routes::Playlists end def self.delete_page(env) - locale = LOCALES[env.get("preferences").as(Preferences).locale]? + locale = env.get("preferences").as(Preferences).locale user = env.get? "user" sid = env.get? "sid" @@ -96,7 +96,7 @@ module Invidious::Routes::Playlists end def self.delete(env) - locale = LOCALES[env.get("preferences").as(Preferences).locale]? + locale = env.get("preferences").as(Preferences).locale user = env.get? "user" sid = env.get? "sid" @@ -129,7 +129,7 @@ module Invidious::Routes::Playlists end def self.edit(env) - locale = LOCALES[env.get("preferences").as(Preferences).locale]? + locale = env.get("preferences").as(Preferences).locale user = env.get? "user" sid = env.get? "sid" @@ -169,7 +169,7 @@ module Invidious::Routes::Playlists end def self.update(env) - locale = LOCALES[env.get("preferences").as(Preferences).locale]? + locale = env.get("preferences").as(Preferences).locale user = env.get? "user" sid = env.get? "sid" @@ -213,7 +213,7 @@ module Invidious::Routes::Playlists end def self.add_playlist_items_page(env) - locale = LOCALES[env.get("preferences").as(Preferences).locale]? + locale = env.get("preferences").as(Preferences).locale user = env.get? "user" sid = env.get? "sid" @@ -260,7 +260,7 @@ module Invidious::Routes::Playlists end def self.playlist_ajax(env) - locale = LOCALES[env.get("preferences").as(Preferences).locale]? + locale = env.get("preferences").as(Preferences).locale user = env.get? "user" sid = env.get? "sid" @@ -387,7 +387,7 @@ module Invidious::Routes::Playlists end def self.show(env) - locale = LOCALES[env.get("preferences").as(Preferences).locale]? + locale = env.get("preferences").as(Preferences).locale user = env.get?("user").try &.as(User) referer = get_referer(env) @@ -435,7 +435,7 @@ module Invidious::Routes::Playlists end def self.mix(env) - locale = LOCALES[env.get("preferences").as(Preferences).locale]? + locale = env.get("preferences").as(Preferences).locale rdid = env.params.query["list"]? if !rdid diff --git a/src/invidious/routes/preferences.cr b/src/invidious/routes/preferences.cr index 8793d4e9..15c00700 100644 --- a/src/invidious/routes/preferences.cr +++ b/src/invidious/routes/preferences.cr @@ -2,7 +2,7 @@ module Invidious::Routes::PreferencesRoute def self.show(env) - locale = LOCALES[env.get("preferences").as(Preferences).locale]? + locale = env.get("preferences").as(Preferences).locale referer = get_referer(env) @@ -12,7 +12,7 @@ module Invidious::Routes::PreferencesRoute end def self.update(env) - locale = LOCALES[env.get("preferences").as(Preferences).locale]? + locale = env.get("preferences").as(Preferences).locale referer = get_referer(env) video_loop = env.params.body["video_loop"]?.try &.as(String) @@ -70,6 +70,10 @@ module Invidious::Routes::PreferencesRoute vr_mode ||= "off" vr_mode = vr_mode == "on" + save_player_pos = env.params.body["save_player_pos"]?.try &.as(String) + save_player_pos ||= "off" + save_player_pos = save_player_pos == "on" + show_nick = env.params.body["show_nick"]?.try &.as(String) show_nick ||= "off" show_nick = show_nick == "on" @@ -165,6 +169,7 @@ module Invidious::Routes::PreferencesRoute extend_desc: extend_desc, vr_mode: vr_mode, show_nick: show_nick, + save_player_pos: save_player_pos, }.to_json).to_json if user = env.get? "user" @@ -227,7 +232,7 @@ module Invidious::Routes::PreferencesRoute end def self.toggle_theme(env) - locale = LOCALES[env.get("preferences").as(Preferences).locale]? + locale = env.get("preferences").as(Preferences).locale referer = get_referer(env, unroll: false) redirect = env.params.query["redirect"]? diff --git a/src/invidious/routes/search.cr b/src/invidious/routes/search.cr index 3f1e219f..c256d156 100644 --- a/src/invidious/routes/search.cr +++ b/src/invidious/routes/search.cr @@ -2,7 +2,7 @@ module Invidious::Routes::Search def self.opensearch(env) - locale = LOCALES[env.get("preferences").as(Preferences).locale]? + locale = env.get("preferences").as(Preferences).locale env.response.content_type = "application/opensearchdescription+xml" XML.build(indent: " ", encoding: "UTF-8") do |xml| @@ -18,7 +18,7 @@ module Invidious::Routes::Search end def self.results(env) - locale = LOCALES[env.get("preferences").as(Preferences).locale]? + locale = env.get("preferences").as(Preferences).locale query = env.params.query["search_query"]? query ||= env.params.query["q"]? @@ -37,7 +37,7 @@ module Invidious::Routes::Search end def self.search(env) - locale = LOCALES[env.get("preferences").as(Preferences).locale]? + locale = env.get("preferences").as(Preferences).locale region = env.params.query["region"]? query = env.params.query["search_query"]? diff --git a/src/invidious/routes/video_playback.cr b/src/invidious/routes/video_playback.cr index 5c64f669..06ba6b8c 100644 --- a/src/invidious/routes/video_playback.cr +++ b/src/invidious/routes/video_playback.cr @@ -1,7 +1,7 @@ module Invidious::Routes::VideoPlayback # /videoplayback def self.get_video_playback(env) - locale = LOCALES[env.get("preferences").as(Preferences).locale]? + locale = env.get("preferences").as(Preferences).locale query_params = env.params.query fvip = query_params["fvip"]? || "3" @@ -240,7 +240,7 @@ module Invidious::Routes::VideoPlayback download_widget = JSON.parse(env.params.query["download_widget"]) id = download_widget["id"].as_s - title = download_widget["title"].as_s + title = URI.decode_www_form(download_widget["title"].as_s) if label = download_widget["label"]? return env.redirect "/api/v1/captions/#{id}?label=#{label}&title=#{title}" diff --git a/src/invidious/routes/watch.cr b/src/invidious/routes/watch.cr index abcf427e..b24222ff 100644 --- a/src/invidious/routes/watch.cr +++ b/src/invidious/routes/watch.cr @@ -2,7 +2,7 @@ module Invidious::Routes::Watch def self.handle(env) - locale = LOCALES[env.get("preferences").as(Preferences).locale]? + locale = env.get("preferences").as(Preferences).locale region = env.params.query["region"]? if env.params.query.to_s.includes?("%20") || env.params.query.to_s.includes?("+") diff --git a/src/invidious/trending.cr b/src/invidious/trending.cr index 25bab4d2..1f957081 100644 --- a/src/invidious/trending.cr +++ b/src/invidious/trending.cr @@ -20,13 +20,3 @@ def fetch_trending(trending_type, region, locale) return {trending, plid} end - -def extract_plid(url) - return url.try { |i| URI.parse(i).query } - .try { |i| HTTP::Params.parse(i)["bp"] } - .try { |i| URI.decode_www_form(i) } - .try { |i| Base64.decode(i) } - .try { |i| IO::Memory.new(i) } - .try { |i| Protodec::Any.parse(i) } - .try &.["44:0:embedded"]?.try &.["2:1:string"]?.try &.as_s -end diff --git a/src/invidious/user/preferences.cr b/src/invidious/user/preferences.cr index c15876f5..bf7ea401 100644 --- a/src/invidious/user/preferences.cr +++ b/src/invidious/user/preferences.cr @@ -53,6 +53,7 @@ struct Preferences property video_loop : Bool = CONFIG.default_user_preferences.video_loop property extend_desc : Bool = CONFIG.default_user_preferences.extend_desc property volume : Int32 = CONFIG.default_user_preferences.volume + property save_player_pos : Bool = CONFIG.default_user_preferences.save_player_pos module BoolToString def self.to_json(value : String, json : JSON::Builder) diff --git a/src/invidious/videos.cr b/src/invidious/videos.cr index 4406284f..d4ef0900 100644 --- a/src/invidious/videos.cr +++ b/src/invidious/videos.cr @@ -246,6 +246,7 @@ struct VideoPreferences property video_start : Float64 | Int32 property volume : Int32 property vr_mode : Bool + property save_player_pos : Bool end struct Video @@ -275,7 +276,7 @@ struct Video end end - def to_json(locale : Hash(String, JSON::Any) | Nil, json : JSON::Builder) + def to_json(locale : String?, json : JSON::Builder) json.object do json.field "type", "video" @@ -475,7 +476,7 @@ struct Video end # TODO: remove the locale and follow the crystal convention - def to_json(locale : Hash(String, JSON::Any) | Nil, _json : Nil) + def to_json(locale : String?, _json : Nil) JSON.build { |json| to_json(locale, json) } end @@ -885,42 +886,84 @@ def extract_video_info(video_id : String, proxy_region : String? = nil, context_ } ).try { |a| JSON::Any.new(a) } || JSON::Any.new([] of JSON::Any) - primary_results = player_response.try &.["contents"]?.try &.["twoColumnWatchNextResults"]?.try &.["results"]? - .try &.["results"]?.try &.["contents"]? - sentiment_bar = primary_results.try &.as_a.select(&.["videoPrimaryInfoRenderer"]?)[0]? - .try &.["videoPrimaryInfoRenderer"]? - .try &.["sentimentBar"]? - .try &.["sentimentBarRenderer"]? - .try &.["tooltip"]? - .try &.as_s - - likes, dislikes = sentiment_bar.try &.split(" / ", 2).map &.gsub(/\D/, "").to_i64 || {0_i64, 0_i64} - params["likes"] = JSON::Any.new(likes) - params["dislikes"] = JSON::Any.new(dislikes) - - params["descriptionHtml"] = JSON::Any.new(primary_results.try &.as_a.select(&.["videoSecondaryInfoRenderer"]?)[0]? - .try &.["videoSecondaryInfoRenderer"]?.try &.["description"]?.try &.["runs"]? - .try &.as_a.try { |t| content_to_comment_html(t).gsub("\n", "<br/>") } || "<p></p>") - - metadata = primary_results.try &.as_a.select(&.["videoSecondaryInfoRenderer"]?)[0]? - .try &.["videoSecondaryInfoRenderer"]? - .try &.["metadataRowContainer"]? - .try &.["metadataRowContainerRenderer"]? - .try &.["rows"]? - .try &.as_a + # Top level elements + + primary_results = player_response + .dig?("contents", "twoColumnWatchNextResults", "results", "results", "contents") + + video_primary_renderer = primary_results + .try &.as_a.find(&.["videoPrimaryInfoRenderer"]?) + .try &.["videoPrimaryInfoRenderer"] + + video_secondary_renderer = primary_results + .try &.as_a.find(&.["videoSecondaryInfoRenderer"]?) + .try &.["videoSecondaryInfoRenderer"] + + # Likes/dislikes + + toplevel_buttons = video_primary_renderer + .try &.dig?("videoActions", "menuRenderer", "topLevelButtons") + + if toplevel_buttons + likes_button = toplevel_buttons.as_a + .find(&.dig("toggleButtonRenderer", "defaultIcon", "iconType").as_s.== "LIKE") + .try &.["toggleButtonRenderer"] + + if likes_button + likes_txt = (likes_button["defaultText"]? || likes_button["toggledText"]?) + .try &.dig?("accessibility", "accessibilityData", "label") + likes = likes_txt.as_s.gsub(/\D/, "").to_i64? if likes_txt + + LOGGER.trace("extract_video_info: Found \"likes\" button. Button text is \"#{likes_txt}\"") + LOGGER.debug("extract_video_info: Likes count is #{likes}") if likes + end + + dislikes_button = toplevel_buttons.as_a + .find(&.dig("toggleButtonRenderer", "defaultIcon", "iconType").as_s.== "DISLIKE") + .try &.["toggleButtonRenderer"] + + if dislikes_button + dislikes_txt = (dislikes_button["defaultText"]? || dislikes_button["toggledText"]?) + .try &.dig?("accessibility", "accessibilityData", "label") + dislikes = dislikes_txt.as_s.gsub(/\D/, "").to_i64? if dislikes_txt + + LOGGER.trace("extract_video_info: Found \"dislikes\" button. Button text is \"#{dislikes_txt}\"") + LOGGER.debug("extract_video_info: Dislikes count is #{dislikes}") if dislikes + end + end + + if likes && likes != 0_i64 && (!dislikes || dislikes == 0_i64) + if rating = player_response.dig?("videoDetails", "averageRating").try { |x| x.as_i64? || x.as_f? } + dislikes = (likes * ((5 - rating)/(rating - 1))).round.to_i64 + LOGGER.debug("extract_video_info: Dislikes count (using fallback method) is #{dislikes}") + end + end + + params["likes"] = JSON::Any.new(likes || 0_i64) + params["dislikes"] = JSON::Any.new(dislikes || 0_i64) + + # Description + + description_html = video_secondary_renderer.try &.dig?("description", "runs") + .try &.as_a.try { |t| content_to_comment_html(t).gsub("\n", "<br/>") } + + params["descriptionHtml"] = JSON::Any.new(description_html || "<p></p>") + + # Video metadata + + metadata = video_secondary_renderer + .try &.dig?("metadataRowContainer", "metadataRowContainerRenderer", "rows") + .try &.as_a params["genre"] = params["microformat"]?.try &.["playerMicroformatRenderer"]?.try &.["category"]? || JSON::Any.new("") params["genreUrl"] = JSON::Any.new(nil) metadata.try &.each do |row| title = row["metadataRowRenderer"]?.try &.["title"]?.try &.["simpleText"]?.try &.as_s - contents = row["metadataRowRenderer"]? - .try &.["contents"]? - .try &.as_a[0]? + contents = row.dig?("metadataRowRenderer", "contents", 0) if title.try &.== "Category" - contents = contents.try &.["runs"]? - .try &.as_a[0]? + contents = contents.try &.dig?("runs", 0) params["genre"] = JSON::Any.new(contents.try &.["text"]?.try &.as_s || "") params["genreUcid"] = JSON::Any.new(contents.try &.["navigationEndpoint"]?.try &.["browseEndpoint"]? @@ -935,17 +978,19 @@ def extract_video_info(video_id : String, proxy_region : String? = nil, context_ end end - author_info = primary_results.try &.as_a.select(&.["videoSecondaryInfoRenderer"]?)[0]? - .try &.["videoSecondaryInfoRenderer"]?.try &.["owner"]?.try &.["videoOwnerRenderer"]? + # Author infos - params["authorThumbnail"] = JSON::Any.new(author_info.try &.["thumbnail"]? - .try &.["thumbnails"]?.try &.as_a[0]?.try &.["url"]? - .try &.as_s || "") + author_info = video_secondary_renderer.try &.dig?("owner", "videoOwnerRenderer") + author_thumbnail = author_info.try &.dig?("thumbnail", "thumbnails", 0, "url") + + params["authorThumbnail"] = JSON::Any.new(author_thumbnail.try &.as_s || "") params["subCountText"] = JSON::Any.new(author_info.try &.["subscriberCountText"]? - .try { |t| t["simpleText"]? || t["runs"]?.try &.[0]?.try &.["text"]? }.try &.as_s.split(" ", 2)[0] || "-") + .try { |t| t["simpleText"]? || t.dig?("runs", 0, "text") }.try &.as_s.split(" ", 2)[0] || "-") + + # Return data - params + return params end def get_video(id, db, refresh = true, region = nil, force_refresh = false) @@ -1046,6 +1091,7 @@ def process_video_params(query, preferences) extend_desc = query["extend_desc"]?.try { |q| (q == "true" || q == "1").to_unsafe } volume = query["volume"]?.try &.to_i? vr_mode = query["vr_mode"]?.try { |q| (q == "true" || q == "1").to_unsafe } + save_player_pos = query["save_player_pos"]?.try { |q| (q == "true" || q == "1").to_unsafe } if preferences # region ||= preferences.region @@ -1066,6 +1112,7 @@ def process_video_params(query, preferences) extend_desc ||= preferences.extend_desc.to_unsafe volume ||= preferences.volume vr_mode ||= preferences.vr_mode.to_unsafe + save_player_pos ||= preferences.save_player_pos.to_unsafe end annotations ||= CONFIG.default_user_preferences.annotations.to_unsafe @@ -1085,6 +1132,7 @@ def process_video_params(query, preferences) extend_desc ||= CONFIG.default_user_preferences.extend_desc.to_unsafe volume ||= CONFIG.default_user_preferences.volume vr_mode ||= CONFIG.default_user_preferences.vr_mode.to_unsafe + save_player_pos ||= CONFIG.default_user_preferences.save_player_pos.to_unsafe annotations = annotations == 1 autoplay = autoplay == 1 @@ -1096,6 +1144,7 @@ def process_video_params(query, preferences) video_loop = video_loop == 1 extend_desc = extend_desc == 1 vr_mode = vr_mode == 1 + save_player_pos = save_player_pos == 1 if CONFIG.disabled?("dash") && quality == "dash" quality = "high" @@ -1146,6 +1195,7 @@ def process_video_params(query, preferences) video_start: video_start, volume: volume, vr_mode: vr_mode, + save_player_pos: save_player_pos, }) return params diff --git a/src/invidious/views/components/player.ecr b/src/invidious/views/components/player.ecr index 6418f66b..206ba380 100644 --- a/src/invidious/views/components/player.ecr +++ b/src/invidious/views/components/player.ecr @@ -32,13 +32,11 @@ <% end %> <% preferred_captions.each do |caption| %> - <track kind="captions" src="/api/v1/captions/<%= video.id %>?label=<%= caption.name %>&hl=<%= env.get("preferences").as(Preferences).locale %>" - label="<%= caption.name %>"> + <track kind="captions" src="/api/v1/captions/<%= video.id %>?label=<%= caption.name %>" label="<%= caption.name %>"> <% end %> <% captions.each do |caption| %> - <track kind="captions" src="/api/v1/captions/<%= video.id %>?label=<%= caption.name %>&hl=<%= env.get("preferences").as(Preferences).locale %>" - label="<%= caption.name %>"> + <track kind="captions" src="/api/v1/captions/<%= video.id %>?label=<%= caption.name %>" label="<%= caption.name %>"> <% end %> <% end %> </video> diff --git a/src/invidious/views/components/video-context-buttons.ecr b/src/invidious/views/components/video-context-buttons.ecr index daa107f0..ddb6c983 100644 --- a/src/invidious/views/components/video-context-buttons.ecr +++ b/src/invidious/views/components/video-context-buttons.ecr @@ -1,6 +1,6 @@ <div class="flex-right"> <div class="icon-buttons"> - <a title="<%=translate(locale, "Watch on YouTube")%>" href="https://www.youtube.com/watch<%=endpoint_params%>"> + <a title="<%=translate(locale, "videoinfo_watch_on_youTube")%>" href="https://www.youtube.com/watch<%=endpoint_params%>"> <i class="icon ion-logo-youtube"></i> </a> <a title="<%=translate(locale, "Audio mode")%>" href="/watch<%=endpoint_params%>&listen=1"> diff --git a/src/invidious/views/feeds/playlists.ecr b/src/invidious/views/feeds/playlists.ecr index 868cfeda..a59344c4 100644 --- a/src/invidious/views/feeds/playlists.ecr +++ b/src/invidious/views/feeds/playlists.ecr @@ -6,7 +6,7 @@ <div class="pure-g h-box"> <div class="pure-u-2-3"> - <h3><%= translate(locale, "`x` created playlists", %(<span id="count">#{items_created.size}</span>)) %></h3> + <h3><%= translate(locale, "user_created_playlists", %(<span id="count">#{items_created.size}</span>)) %></h3> </div> <div class="pure-u-1-3" style="text-align:right"> <h3> @@ -23,7 +23,7 @@ <div class="pure-g h-box"> <div class="pure-u-1"> - <h3><%= translate(locale, "`x` saved playlists", %(<span id="count">#{items_saved.size}</span>)) %></h3> + <h3><%= translate(locale, "user_saved_playlists", %(<span id="count">#{items_saved.size}</span>)) %></h3> </div> </div> diff --git a/src/invidious/views/playlist.ecr b/src/invidious/views/playlist.ecr index d0518de7..136981da 100644 --- a/src/invidious/views/playlist.ecr +++ b/src/invidious/views/playlist.ecr @@ -47,7 +47,7 @@ <%= translate(locale, "Switch Invidious Instance") %> </a> <% else %> - <a href="https://redirect.invidious.io<%= env.request.resource %>"> + <a href="https://redirect.invidious.io/playlist?list=<%= playlist.id %>"> <%= translate(locale, "Switch Invidious Instance") %> </a> <% end %> diff --git a/src/invidious/views/preferences.ecr b/src/invidious/views/preferences.ecr index 374edc99..96904259 100644 --- a/src/invidious/views/preferences.ecr +++ b/src/invidious/views/preferences.ecr @@ -51,7 +51,7 @@ <select name="quality" id="quality"> <% {"dash", "hd720", "medium", "small"}.each do |option| %> <% if !(option == "dash" && CONFIG.disabled?("dash")) %> - <option value="<%= option %>" <% if preferences.quality == option %> selected <% end %>><%= translate(locale, option) %></option> + <option value="<%= option %>" <% if preferences.quality == option %> selected <% end %>><%= translate(locale, "preferences_quality_option_" + option) %></option> <% end %> <% end %> </select> @@ -62,7 +62,7 @@ <label for="quality_dash"><%= translate(locale, "preferences_quality_dash_label") %></label> <select name="quality_dash" id="quality_dash"> <% {"auto", "best", "4320p", "2160p", "1440p", "1080p", "720p", "480p", "360p", "240p", "144p", "worst"}.each do |option| %> - <option value="<%= option %>" <% if preferences.quality_dash == option %> selected <% end %>><%= translate(locale, option) %></option> + <option value="<%= option %>" <% if preferences.quality_dash == option %> selected <% end %>><%= translate(locale, "preferences_quality_dash_option_" + option) %></option> <% end %> </select> </div> @@ -116,13 +116,18 @@ <input name="vr_mode" id="vr_mode" type="checkbox" <% if preferences.vr_mode %>checked<% end %>> </div> + <div class="pure-control-group"> + <label for="save_player_pos"><%= translate(locale, "preferences_save_player_pos_label") %></label> + <input name="save_player_pos" id="save_player_pos" type="checkbox" <% if preferences.save_player_pos %>checked<% end %>> + </div> + <legend><%= translate(locale, "preferences_category_visual") %></legend> <div class="pure-control-group"> <label for="locale"><%= translate(locale, "preferences_locale_label") %></label> <select name="locale" id="locale"> - <% LOCALES.each_key do |option| %> - <option value="<%= option %>" <% if preferences.locale == option %> selected <% end %>><%= option %></option> + <% LOCALES_LIST.each do |iso_name, full_name| %> + <option value="<%= iso_name %>" <% if preferences.locale == iso_name %> selected <% end %>><%= HTML.escape(full_name) %></option> <% end %> </select> </div> diff --git a/src/invidious/views/template.ecr b/src/invidious/views/template.ecr index 3fb2fe18..5b6e6ab8 100644 --- a/src/invidious/views/template.ecr +++ b/src/invidious/views/template.ecr @@ -19,8 +19,10 @@ <link rel="stylesheet" href="/css/default.css?v=<%= ASSET_COMMIT %>"> </head> -<% locale = LOCALES[env.get("preferences").as(Preferences).locale]? %> -<% dark_mode = env.get("preferences").as(Preferences).dark_mode %> +<% + locale = env.get("preferences").as(Preferences).locale + dark_mode = env.get("preferences").as(Preferences).dark_mode +%> <body class="<%= dark_mode.blank? ? "no" : dark_mode %>-theme"> <span style="display:none" id="dark_mode_pref"><%= env.get("preferences").as(Preferences).dark_mode %></span> diff --git a/src/invidious/views/watch.ecr b/src/invidious/views/watch.ecr index 9cf00393..b85ea59d 100644 --- a/src/invidious/views/watch.ecr +++ b/src/invidious/views/watch.ecr @@ -103,7 +103,7 @@ we're going to need to do it here in order to allow for translations. </h3> <% elsif video.live_now %> <h3> - <%= video.premiere_timestamp.try { |t| translate(locale, "Started streaming `x` ago", recode_date((Time.utc - t).ago, locale)) } %> + <%= video.premiere_timestamp.try { |t| translate(locale, "videoinfo_started_streaming_x_ago", recode_date((Time.utc - t).ago, locale)) } %> </h3> <% end %> </div> @@ -112,8 +112,8 @@ we're going to need to do it here in order to allow for translations. <div class="pure-u-1 pure-u-lg-1-5"> <div class="h-box"> <span id="watch-on-youtube"> - <a href="https://www.youtube.com/watch?v=<%= video.id %>"><%= translate(locale, "Watch on YouTube") %></a> - (<a href="https://www.youtube.com/embed/<%= video.id %>"><%= translate(locale, "Embed") %></a>) + <a href="https://www.youtube.com/watch?v=<%= video.id %>"><%= translate(locale, "videoinfo_watch_on_youTube") %></a> + (<a href="https://www.youtube.com/embed/<%= video.id %>"><%= translate(locale, "videoinfo_youTube_embed_link") %></a>) </span> <p id="watch-on-another-invidious-instance"> <% if env.get("preferences").as(Preferences).automatic_instance_redirect%> @@ -123,7 +123,7 @@ we're going to need to do it here in order to allow for translations. <% end %> </p> <p id="embed-link"> - <a href="<%= embed_link %>"><%= translate(locale, "Embed Link") %></a> + <a href="<%= embed_link %>"><%= translate(locale, "videoinfo_invidious_embed_link") %></a> </p> <p id="annotations"> <% if params.annotations %> @@ -189,7 +189,7 @@ we're going to need to do it here in order to allow for translations. <% end %> <% captions.each do |caption| %> <option value='{"id":"<%= video.id %>","label":"<%= caption.name %>","title":"<%= URI.encode_www_form(video.title) %>-<%= video.id %>.<%= caption.language_code %>.vtt"}'> - <%= translate(locale, "Subtitles - `x` (.vtt)", caption.name) %> + <%= translate(locale, "download_subtitles", translate(locale, caption.name)) %> </option> <% end %> </select> diff --git a/src/invidious/yt_backend/extractors.cr b/src/invidious/yt_backend/extractors.cr index 8398ca8e..66b3cdef 100644 --- a/src/invidious/yt_backend/extractors.cr +++ b/src/invidious/yt_backend/extractors.cr @@ -49,6 +49,9 @@ private module Parsers if author_info = item_contents.dig?("ownerText", "runs", 0) author = author_info["text"].as_s author_id = HelperExtractors.get_browse_id(author_info) + elsif author_info = item_contents.dig?("shortBylineText", "runs", 0) + author = author_info["text"].as_s + author_id = HelperExtractors.get_browse_id(author_info) else author = author_fallback.name author_id = author_fallback.id @@ -68,18 +71,25 @@ private module Parsers view_count = item_contents.dig?("viewCountText", "simpleText").try &.as_s.gsub(/\D+/, "").to_i64? || 0_i64 description_html = item_contents["descriptionSnippet"]?.try { |t| parse_content(t) } || "" - # The length information *should* only always exist in "lengthText". However, the legacy Invidious code - # extracts from "thumbnailOverlays" when it doesn't. More testing is needed to see if this is - # actually needed + # The length information generally exist in "lengthText". However, the info can sometimes + # be retrieved from "thumbnailOverlays" (e.g when the video is a "shorts" one). if length_container = item_contents["lengthText"]? length_seconds = decode_length_seconds(length_container["simpleText"].as_s) elsif length_container = item_contents["thumbnailOverlays"]?.try &.as_a.find(&.["thumbnailOverlayTimeStatusRenderer"]?) # This needs to only go down the `simpleText` path (if possible). If more situations came up that requires # a specific pathway then we should add an argument to extract_text that'll make this possible - length_seconds = length_container.dig?("thumbnailOverlayTimeStatusRenderer", "text", "simpleText") + length_text = length_container.dig?("thumbnailOverlayTimeStatusRenderer", "text", "simpleText") - if length_seconds - length_seconds = decode_length_seconds(length_seconds.as_s) + if length_text + length_text = length_text.as_s + + if length_text == "SHORTS" + # Approximate length to one minute, as "shorts" generally don't exceed that length. + # TODO: Add some sort of metadata for the type of video (normal, live, premiere, shorts) + length_seconds = 60_i32 + else + length_seconds = decode_length_seconds(length_text) + end else length_seconds = 0 end |
