diff options
97 files changed, 4147 insertions, 2008 deletions
@@ -77,10 +77,6 @@ Metrics/CyclomaticComplexity: # process_video_params(query, preferences) => [20/10] - src/invidious/videos.cr - # produce_search_params(page, sort, ...) => [29/10] - # process_search_query(query, page, ...) => [14/10] - - src/invidious/search.cr - #src/invidious/playlists.cr:327:5 diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index db0987cf..4e68b7f2 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -38,9 +38,9 @@ jobs: matrix: stable: [true] crystal: - - 1.0.0 - - 1.1.1 - 1.2.2 + - 1.3.2 + - 1.4.0 include: - crystal: nightly stable: false @@ -31,7 +31,7 @@ • <a href="https://instances.invidious.io/">Instances list</a> • - <a href="https://docs.invidious.io/FAQ/">FAQ</a> + <a href="https://docs.invidious.io/faq/">FAQ</a> • <a href="https://docs.invidious.io/">Documentation</a> • @@ -88,7 +88,7 @@ **Technical features** - Embedded video support -- [Developer API](https://docs.invidious.io/API/) +- [Developer API](https://docs.invidious.io/api/) - Does not use official YouTube APIs - No Contributor License Agreement (CLA) @@ -101,7 +101,7 @@ **Hosting invidious:** -- [Follow the installation instructions](https://docs.invidious.io/Installation/) +- [Follow the installation instructions](https://docs.invidious.io/installation/) ## Documentation @@ -119,7 +119,7 @@ embedded youtube videos on other websites with invidious. The documentation contains a list of browser extensions that we recommended to use along with Invidious. -You can read more here: https://docs.invidious.io/Extensions/ +You can read more here: https://docs.invidious.io/applications/ ## Contribute @@ -150,6 +150,7 @@ Weblate also allows you to log-in with major SSO providers like Github, Gitlab, - [PeerTubeify](https://gitlab.com/Cha_deL/peertubeify): On YouTube, displays a link to the same video on PeerTube, if it exists. - [MusicPiped](https://github.com/deep-gaurav/MusicPiped): A material design music player that streams music from YouTube. - [HoloPlay](https://github.com/stephane-r/HoloPlay): Funny Android application connecting on Invidious API's with search, playlists and favorites. +- [WatchTube](https://github.com/WatchTubeTeam/WatchTube): Powerful YouTube client for Apple Watch. ## Liability diff --git a/assets/css/default.css b/assets/css/default.css index 8b2b3578..49069c92 100644 --- a/assets/css/default.css +++ b/assets/css/default.css @@ -15,6 +15,11 @@ body { background-color: rgb(255, 0, 0, 0.5); } +.underlined { + border-bottom: 1px solid; + margin-bottom: 20px; +} + .channel-profile > * { font-size: 1.17em; font-weight: bold; @@ -475,30 +480,6 @@ body.dark-theme { } } -#filters { - display: inline; - margin-top: 15px; -} - -#filters > div { - display: inline-block; -} - -#filters > summary { - display: block; - margin-bottom: 15px; -} - -#filters > summary::before { - content: "[ + ]"; - font-size: 1.5em; -} - -#filters[open] > summary::before { - content: "[ - ]"; - font-size: 1.5em; -} - /*With commit d9528f5 all contents of the page is now within a flexbox. However, the hr element is rendered improperly within one. See https://stackoverflow.com/a/34372979 for more info */ diff --git a/assets/css/player.css b/assets/css/player.css index 120fd2f8..304375b5 100644 --- a/assets/css/player.css +++ b/assets/css/player.css @@ -70,6 +70,9 @@ margin-bottom: 2em; } +.video-js.player-style-youtube .vjs-progress-control .vjs-progress-holder, .video-js.player-style-youtube .vjs-progress-control {height: 5px; +margin-bottom: 10px;} + ul.vjs-menu-content::-webkit-scrollbar { display: none; } diff --git a/assets/css/search.css b/assets/css/search.css new file mode 100644 index 00000000..5ca141d0 --- /dev/null +++ b/assets/css/search.css @@ -0,0 +1,118 @@ +summary { + /* This should hide the marker */ + display: block; + + font-size: 1.17em; + font-weight: bold; + margin: 0 auto 10px auto; + cursor: pointer; +} + +summary::-webkit-details-marker, +summary::marker { display: none; } + +summary:before { + border-radius: 5px; + content: "[ + ]"; + margin: -2px 10px 0 10px; + padding: 1px 0 3px 0; + text-align: center; + width: 40px; +} + +details[open] > summary:before { content: "[ − ]"; } + + +#filters-box { + padding: 10px 20px 20px 10px; + margin: 10px 15px; +} +#filters-flex { + display: flex; + flex-wrap: wrap; + flex-direction: row; + align-items: flex-start; + align-content: flex-start; + justify-content: flex-start; +} + + +fieldset, legend { + display: contents !important; + border: none !important; + margin: 0 !important; + padding: 0 !important; +} + + +.filter-column { + display: inline-block; + display: inline-flex; + width: max-content; + min-width: max-content; + max-width: 16em; + margin: 15px; + flex-grow: 2; + flex-basis: auto; + flex-direction: column; +} +.filter-name, .filter-options { + display: block; + padding: 5px 10px; + margin: 0; + text-align: start; +} + +.filter-options div { margin: 6px 0; } +.filter-options div * { vertical-align: middle; } +.filter-options label { margin: 0 10px; } + + +#filters-apply { text-align: end; } + +/* Error message */ + +.no-results-error { + text-align: center; + line-height: 180%; + font-size: 110%; + padding: 15px 15px 125px 15px; +} + +/* Responsive rules */ + +@media only screen and (max-width: 800px) { + summary { font-size: 1.30em; } + #filters-box { + margin: 10px 0 0 0; + padding: 0; + } + #filters-apply { + text-align: center; + padding: 15px; + } +} + +/* Light theme */ + +.light-theme #filters-box { + background: #dfdfdf; +} + +@media (prefers-color-scheme: light) { + .no-theme #filters-box { + background: #dfdfdf; + } +} + +/* Dark theme */ + +.dark-theme #filters-box { + background: #373737; +} + +@media (prefers-color-scheme: dark) { + .no-theme #filters-box { + background: #373737; + } +} diff --git a/assets/js/community.js b/assets/js/community.js index 4077f1cd..44066a58 100644 --- a/assets/js/community.js +++ b/assets/js/community.js @@ -1,19 +1,20 @@ -var community_data = JSON.parse(document.getElementById('community_data').innerHTML); +'use strict'; +var community_data = JSON.parse(document.getElementById('community_data').textContent); String.prototype.supplant = function (o) { return this.replace(/{([^{}]*)}/g, function (a, b) { var r = o[b]; return typeof r === 'string' || typeof r === 'number' ? r : a; }); -} +}; function hide_youtube_replies(event) { var target = event.target; - sub_text = target.getAttribute('data-inner-text'); - inner_text = target.getAttribute('data-sub-text'); + var sub_text = target.getAttribute('data-inner-text'); + var inner_text = target.getAttribute('data-sub-text'); - body = target.parentNode.parentNode.children[1]; + var body = target.parentNode.parentNode.children[1]; body.style.display = 'none'; target.innerHTML = sub_text; @@ -25,10 +26,10 @@ function hide_youtube_replies(event) { function show_youtube_replies(event) { var target = event.target; - sub_text = target.getAttribute('data-inner-text'); - inner_text = target.getAttribute('data-sub-text'); + var sub_text = target.getAttribute('data-inner-text'); + var inner_text = target.getAttribute('data-sub-text'); - body = target.parentNode.parentNode.children[1]; + var body = target.parentNode.parentNode.children[1]; body.style.display = ''; target.innerHTML = sub_text; @@ -63,8 +64,8 @@ function get_youtube_replies(target, load_more) { xhr.open('GET', url, true); xhr.onreadystatechange = function () { - if (xhr.readyState == 4) { - if (xhr.status == 200) { + if (xhr.readyState === 4) { + if (xhr.status === 200) { if (load_more) { body = body.parentNode.parentNode; body.removeChild(body.lastElementChild); @@ -92,12 +93,12 @@ function get_youtube_replies(target, load_more) { body.innerHTML = fallback; } } - } + }; xhr.ontimeout = function () { - console.log('Pulling comments failed.'); + console.warn('Pulling comments failed.'); body.innerHTML = fallback; - } + }; xhr.send(); } diff --git a/assets/js/embed.js b/assets/js/embed.js index 9d0be0ea..7e9ac605 100644 --- a/assets/js/embed.js +++ b/assets/js/embed.js @@ -1,19 +1,21 @@ -var video_data = JSON.parse(document.getElementById('video_data').innerHTML); +'use strict'; +var video_data = JSON.parse(document.getElementById('video_data').textContent); function get_playlist(plid, retries) { - if (retries == undefined) retries = 5; + if (retries === undefined) retries = 5; if (retries <= 0) { - console.log('Failed to pull playlist'); + console.warn('Failed to pull playlist'); return; } + var plid_url; if (plid.startsWith('RD')) { - var plid_url = '/api/v1/mixes/' + plid + + plid_url = '/api/v1/mixes/' + plid + '?continuation=' + video_data.id + '&format=html&hl=' + video_data.preferences.locale; } else { - var plid_url = '/api/v1/playlists/' + plid + + plid_url = '/api/v1/playlists/' + plid + '?index=' + video_data.index + '&continuation' + video_data.id + '&format=html&hl=' + video_data.preferences.locale; @@ -57,17 +59,17 @@ function get_playlist(plid, retries) { } } } - } + }; xhr.onerror = function () { - console.log('Pulling playlist failed... ' + retries + '/5'); - setTimeout(function () { get_playlist(plid, retries - 1) }, 1000); - } + console.warn('Pulling playlist failed... ' + retries + '/5'); + setTimeout(function () { get_playlist(plid, retries - 1); }, 1000); + }; xhr.ontimeout = function () { - console.log('Pulling playlist failed... ' + retries + '/5'); + console.warn('Pulling playlist failed... ' + retries + '/5'); get_playlist(plid, retries - 1); - } + }; xhr.send(); } @@ -96,7 +98,7 @@ window.addEventListener('load', function (e) { } if (video_data.video_series.length !== 0) { - url.searchParams.set('playlist', video_data.video_series.join(',')) + url.searchParams.set('playlist', video_data.video_series.join(',')); } location.assign(url.pathname + url.search); diff --git a/assets/js/handlers.js b/assets/js/handlers.js index 02175957..3224e668 100644 --- a/assets/js/handlers.js +++ b/assets/js/handlers.js @@ -76,7 +76,7 @@ }); n2a(document.querySelectorAll('[data-onrange="update_volume_value"]')).forEach(function (e) { - var cb = function () { update_volume_value(e); } + var cb = function () { update_volume_value(e); }; e.oninput = cb; e.onchange = cb; }); @@ -102,13 +102,13 @@ xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded'); xhr.onreadystatechange = function () { - if (xhr.readyState == 4) { - if (xhr.status != 200) { + if (xhr.readyState === 4) { + if (xhr.status !== 200) { count.innerText = parseInt(count.innerText) + 1; row.style.display = ''; } } - } + }; var csrf_token = target.parentNode.querySelector('input[name="csrf_token"]').value; xhr.send('csrf_token=' + csrf_token); @@ -131,20 +131,20 @@ xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded'); xhr.onreadystatechange = function () { - if (xhr.readyState == 4) { - if (xhr.status != 200) { + if (xhr.readyState === 4) { + if (xhr.status !== 200) { count.innerText = parseInt(count.innerText) + 1; row.style.display = ''; } } - } + }; var csrf_token = target.parentNode.querySelector('input[name="csrf_token"]').value; xhr.send('csrf_token=' + csrf_token); } // Handle keypresses - window.addEventListener('keydown', (event) => { + window.addEventListener('keydown', function (event) { // Ignore modifier keys if (event.ctrlKey || event.metaKey) return; @@ -152,14 +152,14 @@ let focused_tag = document.activeElement.tagName.toLowerCase(); const allowed = /^(button|checkbox|file|radio|submit)$/; - if (focused_tag === "textarea") return; - if (focused_tag === "input") { + if (focused_tag === 'textarea') return; + if (focused_tag === 'input') { let focused_type = document.activeElement.type.toLowerCase(); if (!focused_type.match(allowed)) return; } // Focus search bar on '/' - if (event.key == "/") { + if (event.key === '/') { document.getElementById('searchbox').focus(); event.preventDefault(); } diff --git a/assets/js/notifications.js b/assets/js/notifications.js index 3d1ec1ed..ec5f6dd3 100644 --- a/assets/js/notifications.js +++ b/assets/js/notifications.js @@ -1,9 +1,10 @@ -var notification_data = JSON.parse(document.getElementById('notification_data').innerHTML); +'use strict'; +var notification_data = JSON.parse(document.getElementById('notification_data').textContent); var notifications, delivered; function get_subscriptions(callback, retries) { - if (retries == undefined) retries = 5; + if (retries === undefined) retries = 5; if (retries <= 0) { return; @@ -17,21 +18,21 @@ function get_subscriptions(callback, retries) { xhr.onreadystatechange = function () { if (xhr.readyState === 4) { if (xhr.status === 200) { - subscriptions = xhr.response; + var subscriptions = xhr.response; callback(subscriptions); } } - } + }; xhr.onerror = function () { - console.log('Pulling subscriptions failed... ' + retries + '/5'); - setTimeout(function () { get_subscriptions(callback, retries - 1) }, 1000); - } + console.warn('Pulling subscriptions failed... ' + retries + '/5'); + setTimeout(function () { get_subscriptions(callback, retries - 1); }, 1000); + }; xhr.ontimeout = function () { - console.log('Pulling subscriptions failed... ' + retries + '/5'); + console.warn('Pulling subscriptions failed... ' + retries + '/5'); get_subscriptions(callback, retries - 1); - } + }; xhr.send(); } @@ -40,7 +41,7 @@ function create_notification_stream(subscriptions) { notifications = new SSE( '/api/v1/auth/notifications?fields=videoId,title,author,authorId,publishedText,published,authorThumbnails,liveNow', { withCredentials: true, - payload: 'topics=' + subscriptions.map(function (subscription) { return subscription.authorId }).join(','), + payload: 'topics=' + subscriptions.map(function (subscription) { return subscription.authorId; }).join(','), headers: { 'Content-Type': 'application/x-www-form-urlencoded' } }); delivered = []; @@ -53,7 +54,7 @@ function create_notification_stream(subscriptions) { } var notification = JSON.parse(event.data); - console.log('Got notification:', notification); + console.info('Got notification:', notification); if (start_time < notification.published && !delivered.includes(notification.videoId)) { if (Notification.permission === 'granted') { @@ -67,7 +68,7 @@ function create_notification_stream(subscriptions) { system_notification.onclick = function (event) { window.open('/watch?v=' + event.currentTarget.tag, '_blank'); - } + }; } delivered.push(notification.videoId); @@ -82,16 +83,16 @@ function create_notification_stream(subscriptions) { '<i class="icon ion-ios-notifications-outline"></i>'; } } - } + }; notifications.addEventListener('error', handle_notification_error); notifications.stream(); } function handle_notification_error(event) { - console.log('Something went wrong with notifications, trying to reconnect...'); + console.warn('Something went wrong with notifications, trying to reconnect...'); notifications = { close: function () { } }; - setTimeout(function () { get_subscriptions(create_notification_stream) }, 1000); + setTimeout(function () { get_subscriptions(create_notification_stream); }, 1000); } window.addEventListener('load', function (e) { diff --git a/assets/js/player.js b/assets/js/player.js index a1a2cd16..6ddb1158 100644 --- a/assets/js/player.js +++ b/assets/js/player.js @@ -1,5 +1,6 @@ -var player_data = JSON.parse(document.getElementById('player_data').innerHTML); -var video_data = JSON.parse(document.getElementById('video_data').innerHTML); +'use strict'; +var player_data = JSON.parse(document.getElementById('player_data').textContent); +var video_data = JSON.parse(document.getElementById('video_data').textContent); var options = { preload: 'auto', @@ -27,7 +28,7 @@ var options = { overrideNative: true } } -} +}; if (player_data.aspect_ratio) { options.aspectRatio = player_data.aspect_ratio; @@ -38,7 +39,7 @@ embed_url.searchParams.delete('v'); var 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 save_player_pos_key = 'save_player_pos'; videojs.Vhs.xhr.beforeRequest = function(options) { if (options.uri.indexOf('videoplayback') === -1 && options.uri.indexOf('local=true') === -1) { @@ -49,6 +50,42 @@ videojs.Vhs.xhr.beforeRequest = function(options) { var player = videojs('player', options); +player.on('error', () => { + if (video_data.params.quality !== 'dash') { + if (!player.currentSrc().includes("local=true") && !video_data.local_disabled) { + var currentSources = player.currentSources(); + for (var i = 0; i < currentSources.length; i++) { + currentSources[i]["src"] += "&local=true" + } + player.src(currentSources) + } + else if (player.error().code === 2 || player.error().code === 4) { + setTimeout(function (event) { + console.log('An error occurred in the player, reloading...'); + + var currentTime = player.currentTime(); + var playbackRate = player.playbackRate(); + var paused = player.paused(); + + player.load(); + + if (currentTime > 0.5) currentTime -= 0.5; + + player.currentTime(currentTime); + player.playbackRate(playbackRate); + + if (!paused) player.play(); + }, 10000); + } + } +}); + +if (video_data.params.quality == 'dash') { + player.reloadSourceOnError({ + errorInterval: 10 + }); +} + /** * Function for add time argument to url * @param {String} url @@ -75,12 +112,12 @@ var shareOptions = { description: player_data.description, image: player_data.thumbnail, get embedCode() { - return "<iframe id='ivplayer' width='640' height='360' src='" + - addCurrentTimeToURL(embed_url) + "' style='border:none;'></iframe>"; + return '<iframe id="ivplayer" width="640" height="360" src="' + + addCurrentTimeToURL(embed_url) + '" style="border:none;"></iframe>'; } }; -const storage = (() => { +const storage = (function () { try { if (localStorage.length !== -1) return localStorage; } catch (e) { console.info('No storage available: ' + e); } @@ -101,78 +138,57 @@ if (location.pathname.startsWith('/embed/')) { // Detection code taken from https://stackoverflow.com/a/20293441 function isMobile() { - try{ document.createEvent("TouchEvent"); return true; } + try{ document.createEvent('TouchEvent'); return true; } catch(e){ return false; } } if (isMobile()) { player.mobileUi(); - buttons = ["playToggle", "volumePanel", "captionsButton"]; + var buttons = ['playToggle', 'volumePanel', 'captionsButton']; - if (video_data.params.quality !== 'dash') buttons.push("qualitySelector") + if (video_data.params.quality !== 'dash') buttons.push('qualitySelector'); // Create new control bar object for operation buttons - const ControlBar = videojs.getComponent("controlBar"); + const ControlBar = videojs.getComponent('controlBar'); let operations_bar = new ControlBar(player, { children: [], playbackRates: [0.25, 0.5, 0.75, 1.0, 1.25, 1.5, 1.75, 2.0] }); - buttons.slice(1).forEach(child => operations_bar.addChild(child)) + buttons.slice(1).forEach(function (child) {operations_bar.addChild(child);}); // Remove operation buttons from primary control bar - primary_control_bar = player.getChild("controlBar"); - buttons.forEach(child => primary_control_bar.removeChild(child)); + var primary_control_bar = player.getChild('controlBar'); + buttons.forEach(function (child) {primary_control_bar.removeChild(child);}); - operations_bar_element = operations_bar.el(); - operations_bar_element.className += " mobile-operations-bar" - player.addChild(operations_bar) + var operations_bar_element = operations_bar.el(); + operations_bar_element.className += ' mobile-operations-bar'; + player.addChild(operations_bar); // Playback menu doesn't work when it's initialized outside of the primary control bar - playback_element = document.getElementsByClassName("vjs-playback-rate")[0] - operations_bar_element.append(playback_element) + var playback_element = document.getElementsByClassName('vjs-playback-rate')[0]; + operations_bar_element.append(playback_element); // The share and http source selector element can't be fetched till the players ready. - player.one("playing", () => { - share_element = document.getElementsByClassName("vjs-share-control")[0] - operations_bar_element.append(share_element) - - if (video_data.params.quality === 'dash') { - http_source_selector = document.getElementsByClassName("vjs-http-source-selector vjs-menu-button")[0] - operations_bar_element.append(http_source_selector) - } - }) -} + player.one('playing', function () { + var share_element = document.getElementsByClassName('vjs-share-control')[0]; + operations_bar_element.append(share_element); -player.on('error', function (event) { - if (player.error().code === 2 || player.error().code === 4) { - setTimeout(function (event) { - console.log('An error occurred in the player, reloading...'); - - var currentTime = player.currentTime(); - var playbackRate = player.playbackRate(); - var paused = player.paused(); - - player.load(); - - if (currentTime > 0.5) currentTime -= 0.5; - - player.currentTime(currentTime); - player.playbackRate(playbackRate); - - if (!paused) player.play(); - }, 5000); - } -}); + if (video_data.params.quality === 'dash') { + var http_source_selector = document.getElementsByClassName('vjs-http-source-selector vjs-menu-button')[0]; + operations_bar_element.append(http_source_selector); + } + }); +} // Enable VR video support if (!video_data.params.listen && video_data.vr && video_data.params.vr_mode) { - player.crossOrigin("anonymous") + player.crossOrigin('anonymous'); switch (video_data.projection_type) { - case "EQUIRECTANGULAR": - player.vr({projection: "equirectangular"}); - default: // Should only be "MESH" but we'll use this as a fallback. - player.vr({projection: "EAC"}); + case 'EQUIRECTANGULAR': + player.vr({projection: 'equirectangular'}); + default: // Should only be 'MESH' but we'll use this as a fallback. + player.vr({projection: 'EAC'}); } } @@ -200,9 +216,71 @@ if (video_data.params.video_start > 0 || video_data.params.video_end > 0) { player.volume(video_data.params.volume / 100); player.playbackRate(video_data.params.speed); +/** + * Method for getting the contents of a cookie + * + * @param {String} name Name of cookie + * @returns cookieValue + */ +function getCookieValue(name) { + var value = document.cookie.split(';').filter(function (item) {return item.includes(name + '=');}); + + return (value.length >= 1) + ? value[0].substring((name + '=').length, value[0].length) + : null; +} + +/** + * Method for updating the 'PREFS' cookie (or creating it if missing) + * + * @param {number} newVolume New volume defined (null if unchanged) + * @param {number} newSpeed New speed defined (null if unchanged) + */ +function updateCookie(newVolume, newSpeed) { + var volumeValue = newVolume !== null ? newVolume : video_data.params.volume; + var speedValue = newSpeed !== null ? newSpeed : video_data.params.speed; + + var cookieValue = getCookieValue('PREFS'); + var cookieData; + + if (cookieValue !== null) { + var cookieJson = JSON.parse(decodeURIComponent(cookieValue)); + cookieJson.volume = volumeValue; + cookieJson.speed = speedValue; + cookieData = encodeURIComponent(JSON.stringify(cookieJson)); + } else { + cookieData = encodeURIComponent(JSON.stringify({ 'volume': volumeValue, 'speed': speedValue })); + } + + // Set expiration in 2 year + var date = new Date(); + date.setTime(date.getTime() + 63115200); + + var ipRegex = /^((\d+\.){3}\d+|[A-Fa-f0-9]*:[A-Fa-f0-9:]*:[A-Fa-f0-9:]+)$/; + var domainUsed = window.location.hostname; + + // Fix for a bug in FF where the leading dot in the FQDN is not ignored + if (domainUsed.charAt(0) !== '.' && !ipRegex.test(domainUsed) && domainUsed !== 'localhost') + domainUsed = '.' + window.location.hostname; + + document.cookie = 'PREFS=' + cookieData + '; SameSite=Strict; path=/; domain=' + + domainUsed + '; expires=' + date.toGMTString() + ';'; + + video_data.params.volume = volumeValue; + video_data.params.speed = speedValue; +} + +player.on('ratechange', function () { + updateCookie(null, player.playbackRate()); +}); + +player.on('volumechange', function () { + updateCookie(Math.ceil(player.volume() * 100), null); +}); + player.on('waiting', function () { if (player.playbackRate() > 1 && player.liveTracker.isLive() && player.liveTracker.atLiveEdge()) { - console.log('Player has caught up to source, resetting playbackRate.') + console.info('Player has caught up to source, resetting playbackRate.'); player.playbackRate(1); } }); @@ -213,13 +291,13 @@ if (video_data.premiere_timestamp && Math.round(new Date() / 1000) < video_data. if (video_data.params.save_player_pos) { const url = new URL(location); - const hasTimeParam = url.searchParams.has("t"); + const hasTimeParam = url.searchParams.has('t'); const remeberedTime = get_video_time(); let lastUpdated = 0; if(!hasTimeParam) set_seconds_after_start(remeberedTime); - const updateTime = () => { + const updateTime = function () { const raw = player.currentTime(); const time = Math.floor(raw); @@ -229,7 +307,7 @@ if (video_data.params.save_player_pos) { } }; - player.on("timeupdate", updateTime); + player.on('timeupdate', updateTime); } else remove_all_video_times(); @@ -239,13 +317,13 @@ if (video_data.params.autoplay) { player.ready(function () { new Promise(function (resolve, reject) { - setTimeout(() => resolve(1), 1); + setTimeout(function () {resolve(1);}, 1); }).then(function (result) { var promise = player.play(); if (promise !== undefined) { - promise.then(_ => { - }).catch(error => { + promise.then(function () { + }).catch(function (error) { bpb.show(); }); } @@ -256,16 +334,16 @@ if (video_data.params.autoplay) { if (!video_data.params.listen && video_data.params.quality === 'dash') { player.httpSourceSelector(); - if (video_data.params.quality_dash != "auto") { - player.ready(() => { - player.on("loadedmetadata", () => { - const qualityLevels = Array.from(player.qualityLevels()).sort((a, b) => a.height - b.height); + if (video_data.params.quality_dash !== 'auto') { + player.ready(function () { + player.on('loadedmetadata', function () { + const qualityLevels = Array.from(player.qualityLevels()).sort(function (a, b) {return a.height - b.height;}); let targetQualityLevel; switch (video_data.params.quality_dash) { - case "best": + case 'best': targetQualityLevel = qualityLevels.length - 1; break; - case "worst": + case 'worst': targetQualityLevel = 0; break; default: @@ -279,7 +357,7 @@ if (!video_data.params.listen && video_data.params.quality === 'dash') { } } for (let i = 0; i < qualityLevels.length; i++) { - qualityLevels[i].enabled = (i == targetQualityLevel); + qualityLevels[i].enabled = (i === targetQualityLevel); } }); }); @@ -313,10 +391,12 @@ if (!video_data.params.listen && video_data.params.annotations) { } } } - } + }; - window.addEventListener('__ar_annotation_click', e => { - const { url, target, seconds } = e.detail; + window.addEventListener('__ar_annotation_click', function (e) { + const url = e.detail.url, + target = e.detail.target, + seconds = e.detail.seconds; var path = new URL(url); if (path.href.startsWith('https://www.youtube.com/watch?') && seconds) { @@ -386,7 +466,7 @@ function get_video_time() { return timestamp || 0; } - catch { + catch (e) { return 0; } } @@ -397,7 +477,7 @@ function set_all_video_times(times) { try { storage.setItem(save_player_pos_key, JSON.stringify(times)); } catch (e) { - console.debug('set_all_video_times: ' + e); + console.warn('set_all_video_times: ' + e); } } else { storage.removeItem(save_player_pos_key); @@ -412,7 +492,7 @@ function get_all_video_times() { try { return JSON.parse(raw); } catch (e) { - console.debug('get_all_video_times: ' + e); + console.warn('get_all_video_times: ' + e); } } } @@ -506,7 +586,7 @@ function increase_playback_rate(steps) { player.playbackRate(options.playbackRates[newIndex]); } -window.addEventListener('keydown', e => { +window.addEventListener('keydown', function (e) { if (e.target.tagName.toLowerCase() === 'input') { // Ignore input when focus is on certain elements, e.g. form fields. return; @@ -625,7 +705,7 @@ window.addEventListener('keydown', e => { var volumeHover = false; var volumeSelector = pEl.querySelector('.vjs-volume-menu-button') || pEl.querySelector('.vjs-volume-panel'); - if (volumeSelector != null) { + if (volumeSelector !== null) { volumeSelector.onmouseover = function () { volumeHover = true; }; volumeSelector.onmouseout = function () { volumeHover = false; }; } @@ -645,9 +725,9 @@ window.addEventListener('keydown', e => { var delta = Math.max(-1, Math.min(1, (event.wheelDelta || -event.detail))); event.preventDefault(); - if (delta == 1) { + if (delta === 1) { increase_volume(volumeStep); - } else if (delta == -1) { + } else if (delta === -1) { increase_volume(-volumeStep); } } @@ -656,7 +736,7 @@ window.addEventListener('keydown', e => { }; player.on('mousewheel', mouseScroll); - player.on("DOMMouseScroll", mouseScroll); + player.on('DOMMouseScroll', mouseScroll); }()); // Since videojs-share can sometimes be blocked, we defer it until last @@ -666,35 +746,35 @@ if (player.share) { // show the preferred caption by default if (player_data.preferred_caption_found) { - player.ready(() => { + player.ready(function () { player.textTracks()[1].mode = 'showing'; }); } // Safari audio double duration fix -if (navigator.vendor == "Apple Computer, Inc." && video_data.params.listen) { +if (navigator.vendor === 'Apple Computer, Inc.' && video_data.params.listen) { player.on('loadedmetadata', function () { player.on('timeupdate', function () { - if (player.remainingTime() < player.duration() / 2) { - player.currentTime(player.duration() + 1); + if (player.remainingTime() < player.duration() / 2 && player.remainingTime() >= 2) { + player.currentTime(player.duration() - 1); } }); }); } // Watch on Invidious link -if (window.location.pathname.startsWith("/embed/")) { +if (window.location.pathname.startsWith('/embed/')) { const Button = videojs.getComponent('Button'); let watch_on_invidious_button = new Button(player); // Create hyperlink for current instance - redirect_element = document.createElement("a"); - redirect_element.setAttribute("href", `http://${window.location.host}/watch?v=${window.location.pathname.replace("/embed/","")}`) - redirect_element.appendChild(document.createTextNode("Invidious")) + var redirect_element = document.createElement('a'); + redirect_element.setAttribute('href', location.pathname.replace('/embed/', '/watch?v=')); + redirect_element.appendChild(document.createTextNode('Invidious')); - watch_on_invidious_button.el().appendChild(redirect_element) - watch_on_invidious_button.addClass("watch-on-invidious") + watch_on_invidious_button.el().appendChild(redirect_element); + watch_on_invidious_button.addClass('watch-on-invidious'); - cb = player.getChild('ControlBar') - cb.addChild(watch_on_invidious_button) -}; + var cb = player.getChild('ControlBar'); + cb.addChild(watch_on_invidious_button); +} diff --git a/assets/js/playlist_widget.js b/assets/js/playlist_widget.js index 0ec27859..c2565874 100644 --- a/assets/js/playlist_widget.js +++ b/assets/js/playlist_widget.js @@ -1,4 +1,5 @@ -var playlist_data = JSON.parse(document.getElementById('playlist_data').innerHTML); +'use strict'; +var playlist_data = JSON.parse(document.getElementById('playlist_data').textContent); function add_playlist_video(target) { var select = target.parentNode.children[0].children[1]; @@ -14,12 +15,12 @@ function add_playlist_video(target) { xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded'); xhr.onreadystatechange = function () { - if (xhr.readyState == 4) { - if (xhr.status == 200) { + if (xhr.readyState === 4) { + if (xhr.status === 200) { option.innerText = '✓' + option.innerText; } } - } + }; xhr.send('csrf_token=' + playlist_data.csrf_token); } @@ -38,12 +39,12 @@ function add_playlist_item(target) { xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded'); xhr.onreadystatechange = function () { - if (xhr.readyState == 4) { - if (xhr.status != 200) { + if (xhr.readyState === 4) { + if (xhr.status !== 200) { tile.style.display = ''; } } - } + }; xhr.send('csrf_token=' + playlist_data.csrf_token); } @@ -62,12 +63,12 @@ function remove_playlist_item(target) { xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded'); xhr.onreadystatechange = function () { - if (xhr.readyState == 4) { - if (xhr.status != 200) { + if (xhr.readyState === 4) { + if (xhr.status !== 200) { tile.style.display = ''; } } - } + }; xhr.send('csrf_token=' + playlist_data.csrf_token); -}
\ No newline at end of file +} diff --git a/assets/js/silvermine-videojs-quality-selector.min.js b/assets/js/silvermine-videojs-quality-selector.min.js index 88621e8d..1877047d 100644 --- a/assets/js/silvermine-videojs-quality-selector.min.js +++ b/assets/js/silvermine-videojs-quality-selector.min.js @@ -1,4 +1,4 @@ -/*! @silvermine/videojs-quality-selector 2020-03-02 v1.1.2-36-g64d620a-dirty */ +/*! @silvermine/videojs-quality-selector 2022-04-13 v1.1.2-43-gaa06e72-dirty */ -!function u(o,c,a){function l(e,n){if(!c[e]){if(!o[e]){var t="function"==typeof require&&require;if(!n&&t)return t(e,!0);if(s)return s(e,!0);var r=new Error("Cannot find module '"+e+"'");throw r.code="MODULE_NOT_FOUND",r}var i=c[e]={exports:{}};o[e][0].call(i.exports,function(n){return l(o[e][1][n]||n)},i,i.exports,u,o,c,a)}return c[e].exports}for(var s="function"==typeof require&&require,n=0;n<a.length;n++)l(a[n]);return l}({1:[function(n,e,t){!function(){var u=!1,o=/xyz/.test(function(){xyz})?/\b_super\b/:/.*/;this.Class=function(){},Class.extend=function(n){var i=this.prototype;u=!0;var e=new this;for(var t in u=!1,n)e[t]="function"==typeof n[t]&&"function"==typeof i[t]&&o.test(n[t])?function(t,r){return function(){var n=this._super;this._super=i[t];var e=r.apply(this,arguments);return this._super=n,e}}(t,n[t]):n[t];function r(){!u&&this.init&&this.init.apply(this,arguments)}return((r.prototype=e).constructor=r).extend=arguments.callee,r},e.exports=Class}()},{}],2:[function(n,J,$){(function(V){!function(){function t(){}var n="object"==typeof self&&self.self===self&&self||"object"==typeof V&&V.global===V&&V||this||{},e=n._,r=Array.prototype,o=Object.prototype,f="undefined"!=typeof Symbol?Symbol.prototype:null,i=r.push,a=r.slice,p=o.toString,u=o.hasOwnProperty,c=Array.isArray,l=Object.keys,s=Object.create,h=function(n){return n instanceof h?n:this instanceof h?void(this._wrapped=n):new h(n)};void 0===$||$.nodeType?n._=h:(void 0!==J&&!J.nodeType&&J.exports&&($=J.exports=h),$._=h),h.VERSION="1.9.1";function d(i,u,n){if(void 0===u)return i;switch(null==n?3:n){case 1:return function(n){return i.call(u,n)};case 3:return function(n,e,t){return i.call(u,n,e,t)};case 4:return function(n,e,t,r){return i.call(u,n,e,t,r)}}return function(){return i.apply(u,arguments)}}function v(n,e,t){return h.iteratee!==y?h.iteratee(n,e):null==n?h.identity:h.isFunction(n)?d(n,e,t):h.isObject(n)&&!h.isArray(n)?h.matcher(n):h.property(n)}var y;h.iteratee=y=function(n,e){return v(n,e,1/0)};function g(i,u){return u=null==u?i.length-1:+u,function(){for(var n=Math.max(arguments.length-u,0),e=Array(n),t=0;t<n;t++)e[t]=arguments[t+u];switch(u){case 0:return i.call(this,e);case 1:return i.call(this,arguments[0],e);case 2:return i.call(this,arguments[0],arguments[1],e)}var r=Array(u+1);for(t=0;t<u;t++)r[t]=arguments[t];return r[u]=e,i.apply(this,r)}}function S(n){if(!h.isObject(n))return{};if(s)return s(n);t.prototype=n;var e=new t;return t.prototype=null,e}function m(e){return function(n){return null==n?void 0:n[e]}}function _(n,e){return null!=n&&u.call(n,e)}function b(n,e){for(var t=e.length,r=0;r<t;r++){if(null==n)return;n=n[e[r]]}return t?n:void 0}function x(n){var e=j(n);return"number"==typeof e&&0<=e&&e<=k}var k=Math.pow(2,53)-1,j=m("length");h.each=h.forEach=function(n,e,t){var r,i;if(e=d(e,t),x(n))for(r=0,i=n.length;r<i;r++)e(n[r],r,n);else{var u=h.keys(n);for(r=0,i=u.length;r<i;r++)e(n[u[r]],u[r],n)}return n},h.map=h.collect=function(n,e,t){e=v(e,t);for(var r=!x(n)&&h.keys(n),i=(r||n).length,u=Array(i),o=0;o<i;o++){var c=r?r[o]:o;u[o]=e(n[c],c,n)}return u};function E(a){return function(n,e,t,r){var i=3<=arguments.length;return function(n,e,t,r){var i=!x(n)&&h.keys(n),u=(i||n).length,o=0<a?0:u-1;for(r||(t=n[i?i[o]:o],o+=a);0<=o&&o<u;o+=a){var c=i?i[o]:o;t=e(t,n[c],c,n)}return t}(n,d(e,r,4),t,i)}}h.reduce=h.foldl=h.inject=E(1),h.reduceRight=h.foldr=E(-1),h.find=h.detect=function(n,e,t){var r=(x(n)?h.findIndex:h.findKey)(n,e,t);if(void 0!==r&&-1!==r)return n[r]},h.filter=h.select=function(n,r,e){var i=[];return r=v(r,e),h.each(n,function(n,e,t){r(n,e,t)&&i.push(n)}),i},h.reject=function(n,e,t){return h.filter(n,h.negate(v(e)),t)},h.every=h.all=function(n,e,t){e=v(e,t);for(var r=!x(n)&&h.keys(n),i=(r||n).length,u=0;u<i;u++){var o=r?r[u]:u;if(!e(n[o],o,n))return!1}return!0},h.some=h.any=function(n,e,t){e=v(e,t);for(var r=!x(n)&&h.keys(n),i=(r||n).length,u=0;u<i;u++){var o=r?r[u]:u;if(e(n[o],o,n))return!0}return!1},h.contains=h.includes=h.include=function(n,e,t,r){return x(n)||(n=h.values(n)),"number"==typeof t&&!r||(t=0),0<=h.indexOf(n,e,t)},h.invoke=g(function(n,t,r){var i,u;return h.isFunction(t)?u=t:h.isArray(t)&&(i=t.slice(0,-1),t=t[t.length-1]),h.map(n,function(n){var e=u;if(!e){if(i&&i.length&&(n=b(n,i)),null==n)return;e=n[t]}return null==e?e:e.apply(n,r)})}),h.pluck=function(n,e){return h.map(n,h.property(e))},h.where=function(n,e){return h.filter(n,h.matcher(e))},h.findWhere=function(n,e){return h.find(n,h.matcher(e))},h.max=function(n,r,e){var t,i,u=-1/0,o=-1/0;if(null==r||"number"==typeof r&&"object"!=typeof n[0]&&null!=n)for(var c=0,a=(n=x(n)?n:h.values(n)).length;c<a;c++)null!=(t=n[c])&&u<t&&(u=t);else r=v(r,e),h.each(n,function(n,e,t){i=r(n,e,t),(o<i||i===-1/0&&u===-1/0)&&(u=n,o=i)});return u},h.min=function(n,r,e){var t,i,u=1/0,o=1/0;if(null==r||"number"==typeof r&&"object"!=typeof n[0]&&null!=n)for(var c=0,a=(n=x(n)?n:h.values(n)).length;c<a;c++)null!=(t=n[c])&&t<u&&(u=t);else r=v(r,e),h.each(n,function(n,e,t){((i=r(n,e,t))<o||i===1/0&&u===1/0)&&(u=n,o=i)});return u},h.shuffle=function(n){return h.sample(n,1/0)},h.sample=function(n,e,t){if(null==e||t)return x(n)||(n=h.values(n)),n[h.random(n.length-1)];var r=x(n)?h.clone(n):h.values(n),i=j(r);e=Math.max(Math.min(e,i),0);for(var u=i-1,o=0;o<e;o++){var c=h.random(o,u),a=r[o];r[o]=r[c],r[c]=a}return r.slice(0,e)},h.sortBy=function(n,r,e){var i=0;return r=v(r,e),h.pluck(h.map(n,function(n,e,t){return{value:n,index:i++,criteria:r(n,e,t)}}).sort(function(n,e){var t=n.criteria,r=e.criteria;if(t!==r){if(r<t||void 0===t)return 1;if(t<r||void 0===r)return-1}return n.index-e.index}),"value")};function w(o,e){return function(r,i,n){var u=e?[[],[]]:{};return i=v(i,n),h.each(r,function(n,e){var t=i(n,e,r);o(u,n,t)}),u}}h.groupBy=w(function(n,e,t){_(n,t)?n[t].push(e):n[t]=[e]}),h.indexBy=w(function(n,e,t){n[t]=e}),h.countBy=w(function(n,e,t){_(n,t)?n[t]++:n[t]=1});var A=/[^\ud800-\udfff]|[\ud800-\udbff][\udc00-\udfff]|[\ud800-\udfff]/g;h.toArray=function(n){return n?h.isArray(n)?a.call(n):h.isString(n)?n.match(A):x(n)?h.map(n,h.identity):h.values(n):[]},h.size=function(n){return null==n?0:x(n)?n.length:h.keys(n).length},h.partition=w(function(n,e,t){n[t?0:1].push(e)},!0),h.first=h.head=h.take=function(n,e,t){return null==n||n.length<1?null==e?void 0:[]:null==e||t?n[0]:h.initial(n,n.length-e)},h.initial=function(n,e,t){return a.call(n,0,Math.max(0,n.length-(null==e||t?1:e)))},h.last=function(n,e,t){return null==n||n.length<1?null==e?void 0:[]:null==e||t?n[n.length-1]:h.rest(n,Math.max(0,n.length-e))},h.rest=h.tail=h.drop=function(n,e,t){return a.call(n,null==e||t?1:e)},h.compact=function(n){return h.filter(n,Boolean)};var T=function(n,e,t,r){for(var i=(r=r||[]).length,u=0,o=j(n);u<o;u++){var c=n[u];if(x(c)&&(h.isArray(c)||h.isArguments(c)))if(e)for(var a=0,l=c.length;a<l;)r[i++]=c[a++];else T(c,e,t,r),i=r.length;else t||(r[i++]=c)}return r};h.flatten=function(n,e){return T(n,e,!1)},h.without=g(function(n,e){return h.difference(n,e)}),h.uniq=h.unique=function(n,e,t,r){h.isBoolean(e)||(r=t,t=e,e=!1),null!=t&&(t=v(t,r));for(var i=[],u=[],o=0,c=j(n);o<c;o++){var a=n[o],l=t?t(a,o,n):a;e&&!t?(o&&u===l||i.push(a),u=l):t?h.contains(u,l)||(u.push(l),i.push(a)):h.contains(i,a)||i.push(a)}return i},h.union=g(function(n){return h.uniq(T(n,!0,!0))}),h.intersection=function(n){for(var e=[],t=arguments.length,r=0,i=j(n);r<i;r++){var u=n[r];if(!h.contains(e,u)){var o;for(o=1;o<t&&h.contains(arguments[o],u);o++);o===t&&e.push(u)}}return e},h.difference=g(function(n,e){return e=T(e,!0,!0),h.filter(n,function(n){return!h.contains(e,n)})}),h.unzip=function(n){for(var e=n&&h.max(n,j).length||0,t=Array(e),r=0;r<e;r++)t[r]=h.pluck(n,r);return t},h.zip=g(h.unzip),h.object=function(n,e){for(var t={},r=0,i=j(n);r<i;r++)e?t[n[r]]=e[r]:t[n[r][0]]=n[r][1];return t};function O(u){return function(n,e,t){e=v(e,t);for(var r=j(n),i=0<u?0:r-1;0<=i&&i<r;i+=u)if(e(n[i],i,n))return i;return-1}}h.findIndex=O(1),h.findLastIndex=O(-1),h.sortedIndex=function(n,e,t,r){for(var i=(t=v(t,r,1))(e),u=0,o=j(n);u<o;){var c=Math.floor((u+o)/2);t(n[c])<i?u=c+1:o=c}return u};function C(u,o,c){return function(n,e,t){var r=0,i=j(n);if("number"==typeof t)0<u?r=0<=t?t:Math.max(t+i,r):i=0<=t?Math.min(t+1,i):t+i+1;else if(c&&t&&i)return n[t=c(n,e)]===e?t:-1;if(e!=e)return 0<=(t=o(a.call(n,r,i),h.isNaN))?t+r:-1;for(t=0<u?r:i-1;0<=t&&t<i;t+=u)if(n[t]===e)return t;return-1}}h.indexOf=C(1,h.findIndex,h.sortedIndex),h.lastIndexOf=C(-1,h.findLastIndex),h.range=function(n,e,t){null==e&&(e=n||0,n=0),t=t||(e<n?-1:1);for(var r=Math.max(Math.ceil((e-n)/t),0),i=Array(r),u=0;u<r;u++,n+=t)i[u]=n;return i},h.chunk=function(n,e){if(null==e||e<1)return[];for(var t=[],r=0,i=n.length;r<i;)t.push(a.call(n,r,r+=e));return t};function I(n,e,t,r,i){if(!(r instanceof e))return n.apply(t,i);var u=S(n.prototype),o=n.apply(u,i);return h.isObject(o)?o:u}h.bind=g(function(e,t,r){if(!h.isFunction(e))throw new TypeError("Bind must be called on a function");var i=g(function(n){return I(e,i,t,this,r.concat(n))});return i}),h.partial=g(function(i,u){var o=h.partial.placeholder,c=function(){for(var n=0,e=u.length,t=Array(e),r=0;r<e;r++)t[r]=u[r]===o?arguments[n++]:u[r];for(;n<arguments.length;)t.push(arguments[n++]);return I(i,c,this,this,t)};return c}),(h.partial.placeholder=h).bindAll=g(function(n,e){var t=(e=T(e,!1,!1)).length;if(t<1)throw new Error("bindAll must be passed function names");for(;t--;){var r=e[t];n[r]=h.bind(n[r],n)}}),h.memoize=function(r,i){var u=function(n){var e=u.cache,t=""+(i?i.apply(this,arguments):n);return _(e,t)||(e[t]=r.apply(this,arguments)),e[t]};return u.cache={},u},h.delay=g(function(n,e,t){return setTimeout(function(){return n.apply(null,t)},e)}),h.defer=h.partial(h.delay,h,1),h.throttle=function(t,r,i){var u,o,c,a,l=0;i=i||{};function s(){l=!1===i.leading?0:h.now(),u=null,a=t.apply(o,c),u||(o=c=null)}function n(){var n=h.now();l||!1!==i.leading||(l=n);var e=r-(n-l);return o=this,c=arguments,e<=0||r<e?(u&&(clearTimeout(u),u=null),l=n,a=t.apply(o,c),u||(o=c=null)):u||!1===i.trailing||(u=setTimeout(s,e)),a}return n.cancel=function(){clearTimeout(u),l=0,u=o=c=null},n},h.debounce=function(t,r,i){function u(n,e){o=null,e&&(c=t.apply(n,e))}var o,c,n=g(function(n){if(o&&clearTimeout(o),i){var e=!o;o=setTimeout(u,r),e&&(c=t.apply(this,n))}else o=h.delay(u,r,this,n);return c});return n.cancel=function(){clearTimeout(o),o=null},n},h.wrap=function(n,e){return h.partial(e,n)},h.negate=function(n){return function(){return!n.apply(this,arguments)}},h.compose=function(){var t=arguments,r=t.length-1;return function(){for(var n=r,e=t[r].apply(this,arguments);n--;)e=t[n].call(this,e);return e}},h.after=function(n,e){return function(){if(--n<1)return e.apply(this,arguments)}},h.before=function(n,e){var t;return function(){return 0<--n&&(t=e.apply(this,arguments)),n<=1&&(e=null),t}},h.once=h.partial(h.before,2),h.restArguments=g;function F(n,e){var t=M.length,r=n.constructor,i=h.isFunction(r)&&r.prototype||o,u="constructor";for(_(n,u)&&!h.contains(e,u)&&e.push(u);t--;)(u=M[t])in n&&n[u]!==i[u]&&!h.contains(e,u)&&e.push(u)}var q=!{toString:null}.propertyIsEnumerable("toString"),M=["valueOf","isPrototypeOf","toString","propertyIsEnumerable","hasOwnProperty","toLocaleString"];h.keys=function(n){if(!h.isObject(n))return[];if(l)return l(n);var e=[];for(var t in n)_(n,t)&&e.push(t);return q&&F(n,e),e},h.allKeys=function(n){if(!h.isObject(n))return[];var e=[];for(var t in n)e.push(t);return q&&F(n,e),e},h.values=function(n){for(var e=h.keys(n),t=e.length,r=Array(t),i=0;i<t;i++)r[i]=n[e[i]];return r},h.mapObject=function(n,e,t){e=v(e,t);for(var r=h.keys(n),i=r.length,u={},o=0;o<i;o++){var c=r[o];u[c]=e(n[c],c,n)}return u},h.pairs=function(n){for(var e=h.keys(n),t=e.length,r=Array(t),i=0;i<t;i++)r[i]=[e[i],n[e[i]]];return r},h.invert=function(n){for(var e={},t=h.keys(n),r=0,i=t.length;r<i;r++)e[n[t[r]]]=t[r];return e},h.functions=h.methods=function(n){var e=[];for(var t in n)h.isFunction(n[t])&&e.push(t);return e.sort()};function N(a,l){return function(n){var e=arguments.length;if(l&&(n=Object(n)),e<2||null==n)return n;for(var t=1;t<e;t++)for(var r=arguments[t],i=a(r),u=i.length,o=0;o<u;o++){var c=i[o];l&&void 0!==n[c]||(n[c]=r[c])}return n}}h.extend=N(h.allKeys),h.extendOwn=h.assign=N(h.keys),h.findKey=function(n,e,t){e=v(e,t);for(var r,i=h.keys(n),u=0,o=i.length;u<o;u++)if(e(n[r=i[u]],r,n))return r};function R(n,e,t){return e in t}var Q,L;h.pick=g(function(n,e){var t={},r=e[0];if(null==n)return t;h.isFunction(r)?(1<e.length&&(r=d(r,e[1])),e=h.allKeys(n)):(r=R,e=T(e,!1,!1),n=Object(n));for(var i=0,u=e.length;i<u;i++){var o=e[i],c=n[o];r(c,o,n)&&(t[o]=c)}return t}),h.omit=g(function(n,t){var e,r=t[0];return h.isFunction(r)?(r=h.negate(r),1<t.length&&(e=t[1])):(t=h.map(T(t,!1,!1),String),r=function(n,e){return!h.contains(t,e)}),h.pick(n,r,e)}),h.defaults=N(h.allKeys,!0),h.create=function(n,e){var t=S(n);return e&&h.extendOwn(t,e),t},h.clone=function(n){return h.isObject(n)?h.isArray(n)?n.slice():h.extend({},n):n},h.tap=function(n,e){return e(n),n},h.isMatch=function(n,e){var t=h.keys(e),r=t.length;if(null==n)return!r;for(var i=Object(n),u=0;u<r;u++){var o=t[u];if(e[o]!==i[o]||!(o in i))return!1}return!0},Q=function(n,e,t,r){if(n===e)return 0!==n||1/n==1/e;if(null==n||null==e)return!1;if(n!=n)return e!=e;var i=typeof n;return("function"==i||"object"==i||"object"==typeof e)&&L(n,e,t,r)},L=function(n,e,t,r){n instanceof h&&(n=n._wrapped),e instanceof h&&(e=e._wrapped);var i=p.call(n);if(i!==p.call(e))return!1;switch(i){case"[object RegExp]":case"[object String]":return""+n==""+e;case"[object Number]":return+n!=+n?+e!=+e:0==+n?1/+n==1/e:+n==+e;case"[object Date]":case"[object Boolean]":return+n==+e;case"[object Symbol]":return f.valueOf.call(n)===f.valueOf.call(e)}var u="[object Array]"===i;if(!u){if("object"!=typeof n||"object"!=typeof e)return!1;var o=n.constructor,c=e.constructor;if(o!==c&&!(h.isFunction(o)&&o instanceof o&&h.isFunction(c)&&c instanceof c)&&"constructor"in n&&"constructor"in e)return!1}r=r||[];for(var a=(t=t||[]).length;a--;)if(t[a]===n)return r[a]===e;if(t.push(n),r.push(e),u){if((a=n.length)!==e.length)return!1;for(;a--;)if(!Q(n[a],e[a],t,r))return!1}else{var l,s=h.keys(n);if(a=s.length,h.keys(e).length!==a)return!1;for(;a--;)if(l=s[a],!_(e,l)||!Q(n[l],e[l],t,r))return!1}return t.pop(),r.pop(),!0},h.isEqual=function(n,e){return Q(n,e)},h.isEmpty=function(n){return null==n||(x(n)&&(h.isArray(n)||h.isString(n)||h.isArguments(n))?0===n.length:0===h.keys(n).length)},h.isElement=function(n){return!(!n||1!==n.nodeType)},h.isArray=c||function(n){return"[object Array]"===p.call(n)},h.isObject=function(n){var e=typeof n;return"function"==e||"object"==e&&!!n},h.each(["Arguments","Function","String","Number","Date","RegExp","Error","Symbol","Map","WeakMap","Set","WeakSet"],function(e){h["is"+e]=function(n){return p.call(n)==="[object "+e+"]"}}),h.isArguments(arguments)||(h.isArguments=function(n){return _(n,"callee")});var U=n.document&&n.document.childNodes;"function"!=typeof/./&&"object"!=typeof Int8Array&&"function"!=typeof U&&(h.isFunction=function(n){return"function"==typeof n||!1}),h.isFinite=function(n){return!h.isSymbol(n)&&isFinite(n)&&!isNaN(parseFloat(n))},h.isNaN=function(n){return h.isNumber(n)&&isNaN(n)},h.isBoolean=function(n){return!0===n||!1===n||"[object Boolean]"===p.call(n)},h.isNull=function(n){return null===n},h.isUndefined=function(n){return void 0===n},h.has=function(n,e){if(!h.isArray(e))return _(n,e);for(var t=e.length,r=0;r<t;r++){var i=e[r];if(null==n||!u.call(n,i))return!1;n=n[i]}return!!t},h.noConflict=function(){return n._=e,this},h.identity=function(n){return n},h.constant=function(n){return function(){return n}},h.noop=function(){},h.property=function(e){return h.isArray(e)?function(n){return b(n,e)}:m(e)},h.propertyOf=function(e){return null==e?function(){}:function(n){return h.isArray(n)?b(e,n):e[n]}},h.matcher=h.matches=function(e){return e=h.extendOwn({},e),function(n){return h.isMatch(n,e)}},h.times=function(n,e,t){var r=Array(Math.max(0,n));e=d(e,t,1);for(var i=0;i<n;i++)r[i]=e(i);return r},h.random=function(n,e){return null==e&&(e=n,n=0),n+Math.floor(Math.random()*(e-n+1))},h.now=Date.now||function(){return(new Date).getTime()};function D(e){function t(n){return e[n]}var n="(?:"+h.keys(e).join("|")+")",r=RegExp(n),i=RegExp(n,"g");return function(n){return n=null==n?"":""+n,r.test(n)?n.replace(i,t):n}}var P={"&":"&","<":"<",">":">",'"':""","'":"'","`":"`"},W=h.invert(P);h.escape=D(P),h.unescape=D(W),h.result=function(n,e,t){h.isArray(e)||(e=[e]);var r=e.length;if(!r)return h.isFunction(t)?t.call(n):t;for(var i=0;i<r;i++){var u=null==n?void 0:n[e[i]];void 0===u&&(u=t,i=r),n=h.isFunction(u)?u.call(n):u}return n};var B=0;h.uniqueId=function(n){var e=++B+"";return n?n+e:e},h.templateSettings={evaluate:/<%([\s\S]+?)%>/g,interpolate:/<%=([\s\S]+?)%>/g,escape:/<%-([\s\S]+?)%>/g};function Y(n){return"\\"+K[n]}var z=/(.)^/,K={"'":"'","\\":"\\","\r":"r","\n":"n","\u2028":"u2028","\u2029":"u2029"},G=/\\|'|\r|\n|\u2028|\u2029/g;h.template=function(u,n,e){!n&&e&&(n=e),n=h.defaults({},n,h.templateSettings);var t,r=RegExp([(n.escape||z).source,(n.interpolate||z).source,(n.evaluate||z).source].join("|")+"|$","g"),o=0,c="__p+='";u.replace(r,function(n,e,t,r,i){return c+=u.slice(o,i).replace(G,Y),o=i+n.length,e?c+="'+\n((__t=("+e+"))==null?'':_.escape(__t))+\n'":t?c+="'+\n((__t=("+t+"))==null?'':__t)+\n'":r&&(c+="';\n"+r+"\n__p+='"),n}),c+="';\n",n.variable||(c="with(obj||{}){\n"+c+"}\n"),c="var __t,__p='',__j=Array.prototype.join,print=function(){__p+=__j.call(arguments,'');};\n"+c+"return __p;\n";try{t=new Function(n.variable||"obj","_",c)}catch(n){throw n.source=c,n}function i(n){return t.call(this,n,h)}var a=n.variable||"obj";return i.source="function("+a+"){\n"+c+"}",i},h.chain=function(n){var e=h(n);return e._chain=!0,e};function H(n,e){return n._chain?h(e).chain():e}h.mixin=function(t){return h.each(h.functions(t),function(n){var e=h[n]=t[n];h.prototype[n]=function(){var n=[this._wrapped];return i.apply(n,arguments),H(this,e.apply(h,n))}}),h},h.mixin(h),h.each(["pop","push","reverse","shift","sort","splice","unshift"],function(e){var t=r[e];h.prototype[e]=function(){var n=this._wrapped;return t.apply(n,arguments),"shift"!==e&&"splice"!==e||0!==n.length||delete n[0],H(this,n)}}),h.each(["concat","join","slice"],function(n){var e=r[n];h.prototype[n]=function(){return H(this,e.apply(this._wrapped,arguments))}}),h.prototype.value=function(){return this._wrapped},h.prototype.valueOf=h.prototype.toJSON=h.prototype.value,h.prototype.toString=function(){return String(this._wrapped)},"function"==typeof define&&define.amd&&define("underscore",[],function(){return h})}()}).call(this,"undefined"!=typeof global?global:"undefined"!=typeof self?self:"undefined"!=typeof window?window:{})},{}],3:[function(n,e,t){"use strict";var i=n("underscore"),u=n("../events");e.exports=function(n){var r=n.getComponent("MenuItem");return n.extend(r,{constructor:function(n,e){var t=e.source;if(!i.isObject(t))throw new Error('was not provided a "source" object, but rather: '+typeof t);e=i.extend({selectable:!0,label:t.label},e),r.call(this,n,e),this.source=t},handleClick:function(n){r.prototype.handleClick.call(this,n),this.player().trigger(u.QUALITY_REQUESTED,this.source)}})}},{"../events":5,underscore:2}],4:[function(n,e,t){"use strict";var i=n("underscore"),u=n("../events"),o=n("./QualityOption"),c="vjs-quality-changing";e.exports=function(n){var e,r=n.getComponent("MenuButton"),t=o(n);return e=n.extend(r,{constructor:function(t,n){r.call(this,t,n),t.on(u.QUALITY_REQUESTED,function(n,e){this.setSelectedSource(e),t.addClass(c),t.one("loadeddata",function(){t.removeClass(c)})}.bind(this)),t.on(u.PLAYER_SOURCES_CHANGED,function(){this.update()}.bind(this)),t.on(u.QUALITY_SELECTED,function(n,e){this.setSelectedSource(e)}.bind(this)),t.one("ready",function(){this.selectedSrc=t.src(),this.update()}.bind(this)),this.controlText("Open quality selector menu")},setSelectedSource:function(n){var e=n?n.src:void 0;this.selectedSrc!==e&&(this.selectedSrc=e,i.each(this.items,function(n){n.selected(n.source.src===e)}))},createItems:function(){var e=this.player(),n=e.currentSources();return i.map(n,function(n){return new t(e,{source:n,selected:n.src===this.selectedSrc})}.bind(this))},buildWrapperCSSClass:function(){return"vjs-quality-selector "+r.prototype.buildWrapperCSSClass.call(this)}}),n.registerComponent("QualitySelector",e),e}},{"../events":5,"./QualityOption":3,underscore:2}],5:[function(n,e,t){"use strict";e.exports={QUALITY_REQUESTED:"qualityRequested",QUALITY_SELECTED:"qualitySelected",PLAYER_SOURCES_CHANGED:"playerSourcesChanged"}},{}],6:[function(n,e,t){"use strict";var c=n("underscore"),r=n("./events"),i=n("./components/QualitySelector"),u=n("./middleware/SourceInterceptor"),a=n("./util/SafeSeek");e.exports=function(n){n=n||window.videojs,i(n),u(n),n.hook("setup",function(o){o.on(r.QUALITY_REQUESTED,function(n,e){var t=o.currentSources(),r=o.currentTime(),i=o.playbackRate(),u=o.paused();c.each(t,function(n){n.selected=!1}),c.findWhere(t,{src:e.src}).selected=!0,o._qualitySelectorSafeSeek&&o._qualitySelectorSafeSeek.onQualitySelectionChange(),o.src(t),o.ready(function(){o._qualitySelectorSafeSeek&&!o._qualitySelectorSafeSeek.hasFinished()||(o._qualitySelectorSafeSeek=new a(o,r),o.playbackRate(i)),u||o.play()})})})},e.exports.EVENTS=r},{"./components/QualitySelector":4,"./events":5,"./middleware/SourceInterceptor":7,"./util/SafeSeek":9,underscore:2}],7:[function(n,e,t){"use strict";var u=n("underscore"),o=n("../events");e.exports=function(n){n.use("*",function(i){return{setSource:function(n,e){var t,r=i.currentSources();i._qualitySelectorSafeSeek&&i._qualitySelectorSafeSeek.onPlayerSourcesChange(),u.isEqual(r,i._qualitySelectorPreviousSources)||(i.trigger(o.PLAYER_SOURCES_CHANGED,r),i._qualitySelectorPreviousSources=r),t=u.find(r,function(n){return!0===n.selected||"true"===n.selected||"selected"===n.selected})||n,i.trigger(o.QUALITY_SELECTED,t),e(null,t)}}})}},{"../events":5,underscore:2}],8:[function(n,e,t){"use strict";n("./index")()},{"./index":6}],9:[function(n,e,t){"use strict";var r=n("class.extend");e.exports=r.extend({init:function(n,e){this._player=n,this._seekToTime=e,this._hasFinished=!1,this._keepThisInstanceWhenPlayerSourcesChange=!1,this._seekWhenSafe()},_seekWhenSafe:function(){this._player.readyState()<3?(this._seekFn=this._seek.bind(this),this._player.one("canplay",this._seekFn)):this._seek()},onPlayerSourcesChange:function(){this._keepThisInstanceWhenPlayerSourcesChange?this._keepThisInstanceWhenPlayerSourcesChange=!1:this.cancel()},onQualitySelectionChange:function(){this.hasFinished()||(this._keepThisInstanceWhenPlayerSourcesChange=!0)},_seek:function(){this._player.currentTime(this._seekToTime),this._keepThisInstanceWhenPlayerSourcesChange=!1,this._hasFinished=!0},hasFinished:function(){return this._hasFinished},cancel:function(){this._player.off("canplay",this._seekFn),this._keepThisInstanceWhenPlayerSourcesChange=!1,this._hasFinished=!0}})},{"class.extend":1}]},{},[8]); +!function u(o,c,a){function l(e,n){if(!c[e]){if(!o[e]){var t="function"==typeof require&&require;if(!n&&t)return t(e,!0);if(s)return s(e,!0);var r=new Error("Cannot find module '"+e+"'");throw r.code="MODULE_NOT_FOUND",r}var i=c[e]={exports:{}};o[e][0].call(i.exports,function(n){return l(o[e][1][n]||n)},i,i.exports,u,o,c,a)}return c[e].exports}for(var s="function"==typeof require&&require,n=0;n<a.length;n++)l(a[n]);return l}({1:[function(n,e,t){!function(){var u=!1,o=/xyz/.test(function(){xyz})?/\b_super\b/:/.*/;this.Class=function(){},Class.extend=function(n){var i=this.prototype;u=!0;var e=new this;for(var t in u=!1,n)e[t]="function"==typeof n[t]&&"function"==typeof i[t]&&o.test(n[t])?function(t,r){return function(){var n=this._super;this._super=i[t];var e=r.apply(this,arguments);return this._super=n,e}}(t,n[t]):n[t];function r(){!u&&this.init&&this.init.apply(this,arguments)}return((r.prototype=e).constructor=r).extend=arguments.callee,r},e.exports=Class}()},{}],2:[function(n,J,$){(function(V){!function(){function t(){}var n="object"==typeof self&&self.self===self&&self||"object"==typeof V&&V.global===V&&V||this||{},e=n._,r=Array.prototype,o=Object.prototype,f="undefined"!=typeof Symbol?Symbol.prototype:null,i=r.push,a=r.slice,p=o.toString,u=o.hasOwnProperty,c=Array.isArray,l=Object.keys,s=Object.create,h=function(n){return n instanceof h?n:this instanceof h?void(this._wrapped=n):new h(n)};void 0===$||$.nodeType?n._=h:(void 0!==J&&!J.nodeType&&J.exports&&($=J.exports=h),$._=h),h.VERSION="1.9.1";function d(i,u,n){if(void 0===u)return i;switch(null==n?3:n){case 1:return function(n){return i.call(u,n)};case 3:return function(n,e,t){return i.call(u,n,e,t)};case 4:return function(n,e,t,r){return i.call(u,n,e,t,r)}}return function(){return i.apply(u,arguments)}}function y(n,e,t){return h.iteratee!==v?h.iteratee(n,e):null==n?h.identity:h.isFunction(n)?d(n,e,t):h.isObject(n)&&!h.isArray(n)?h.matcher(n):h.property(n)}var v;h.iteratee=v=function(n,e){return y(n,e,1/0)};function g(i,u){return u=null==u?i.length-1:+u,function(){for(var n=Math.max(arguments.length-u,0),e=Array(n),t=0;t<n;t++)e[t]=arguments[t+u];switch(u){case 0:return i.call(this,e);case 1:return i.call(this,arguments[0],e);case 2:return i.call(this,arguments[0],arguments[1],e)}var r=Array(u+1);for(t=0;t<u;t++)r[t]=arguments[t];return r[u]=e,i.apply(this,r)}}function S(n){if(!h.isObject(n))return{};if(s)return s(n);t.prototype=n;var e=new t;return t.prototype=null,e}function m(e){return function(n){return null==n?void 0:n[e]}}function _(n,e){return null!=n&&u.call(n,e)}function b(n,e){for(var t=e.length,r=0;r<t;r++){if(null==n)return;n=n[e[r]]}return t?n:void 0}function x(n){var e=j(n);return"number"==typeof e&&0<=e&&e<=k}var k=Math.pow(2,53)-1,j=m("length");h.each=h.forEach=function(n,e,t){var r,i;if(e=d(e,t),x(n))for(r=0,i=n.length;r<i;r++)e(n[r],r,n);else{var u=h.keys(n);for(r=0,i=u.length;r<i;r++)e(n[u[r]],u[r],n)}return n},h.map=h.collect=function(n,e,t){e=y(e,t);for(var r=!x(n)&&h.keys(n),i=(r||n).length,u=Array(i),o=0;o<i;o++){var c=r?r[o]:o;u[o]=e(n[c],c,n)}return u};function E(a){return function(n,e,t,r){var i=3<=arguments.length;return function(n,e,t,r){var i=!x(n)&&h.keys(n),u=(i||n).length,o=0<a?0:u-1;for(r||(t=n[i?i[o]:o],o+=a);0<=o&&o<u;o+=a){var c=i?i[o]:o;t=e(t,n[c],c,n)}return t}(n,d(e,r,4),t,i)}}h.reduce=h.foldl=h.inject=E(1),h.reduceRight=h.foldr=E(-1),h.find=h.detect=function(n,e,t){var r=(x(n)?h.findIndex:h.findKey)(n,e,t);if(void 0!==r&&-1!==r)return n[r]},h.filter=h.select=function(n,r,e){var i=[];return r=y(r,e),h.each(n,function(n,e,t){r(n,e,t)&&i.push(n)}),i},h.reject=function(n,e,t){return h.filter(n,h.negate(y(e)),t)},h.every=h.all=function(n,e,t){e=y(e,t);for(var r=!x(n)&&h.keys(n),i=(r||n).length,u=0;u<i;u++){var o=r?r[u]:u;if(!e(n[o],o,n))return!1}return!0},h.some=h.any=function(n,e,t){e=y(e,t);for(var r=!x(n)&&h.keys(n),i=(r||n).length,u=0;u<i;u++){var o=r?r[u]:u;if(e(n[o],o,n))return!0}return!1},h.contains=h.includes=h.include=function(n,e,t,r){return x(n)||(n=h.values(n)),"number"==typeof t&&!r||(t=0),0<=h.indexOf(n,e,t)},h.invoke=g(function(n,t,r){var i,u;return h.isFunction(t)?u=t:h.isArray(t)&&(i=t.slice(0,-1),t=t[t.length-1]),h.map(n,function(n){var e=u;if(!e){if(i&&i.length&&(n=b(n,i)),null==n)return;e=n[t]}return null==e?e:e.apply(n,r)})}),h.pluck=function(n,e){return h.map(n,h.property(e))},h.where=function(n,e){return h.filter(n,h.matcher(e))},h.findWhere=function(n,e){return h.find(n,h.matcher(e))},h.max=function(n,r,e){var t,i,u=-1/0,o=-1/0;if(null==r||"number"==typeof r&&"object"!=typeof n[0]&&null!=n)for(var c=0,a=(n=x(n)?n:h.values(n)).length;c<a;c++)null!=(t=n[c])&&u<t&&(u=t);else r=y(r,e),h.each(n,function(n,e,t){i=r(n,e,t),(o<i||i===-1/0&&u===-1/0)&&(u=n,o=i)});return u},h.min=function(n,r,e){var t,i,u=1/0,o=1/0;if(null==r||"number"==typeof r&&"object"!=typeof n[0]&&null!=n)for(var c=0,a=(n=x(n)?n:h.values(n)).length;c<a;c++)null!=(t=n[c])&&t<u&&(u=t);else r=y(r,e),h.each(n,function(n,e,t){((i=r(n,e,t))<o||i===1/0&&u===1/0)&&(u=n,o=i)});return u},h.shuffle=function(n){return h.sample(n,1/0)},h.sample=function(n,e,t){if(null==e||t)return x(n)||(n=h.values(n)),n[h.random(n.length-1)];var r=x(n)?h.clone(n):h.values(n),i=j(r);e=Math.max(Math.min(e,i),0);for(var u=i-1,o=0;o<e;o++){var c=h.random(o,u),a=r[o];r[o]=r[c],r[c]=a}return r.slice(0,e)},h.sortBy=function(n,r,e){var i=0;return r=y(r,e),h.pluck(h.map(n,function(n,e,t){return{value:n,index:i++,criteria:r(n,e,t)}}).sort(function(n,e){var t=n.criteria,r=e.criteria;if(t!==r){if(r<t||void 0===t)return 1;if(t<r||void 0===r)return-1}return n.index-e.index}),"value")};function w(o,e){return function(r,i,n){var u=e?[[],[]]:{};return i=y(i,n),h.each(r,function(n,e){var t=i(n,e,r);o(u,n,t)}),u}}h.groupBy=w(function(n,e,t){_(n,t)?n[t].push(e):n[t]=[e]}),h.indexBy=w(function(n,e,t){n[t]=e}),h.countBy=w(function(n,e,t){_(n,t)?n[t]++:n[t]=1});var A=/[^\ud800-\udfff]|[\ud800-\udbff][\udc00-\udfff]|[\ud800-\udfff]/g;h.toArray=function(n){return n?h.isArray(n)?a.call(n):h.isString(n)?n.match(A):x(n)?h.map(n,h.identity):h.values(n):[]},h.size=function(n){return null==n?0:x(n)?n.length:h.keys(n).length},h.partition=w(function(n,e,t){n[t?0:1].push(e)},!0),h.first=h.head=h.take=function(n,e,t){return null==n||n.length<1?null==e?void 0:[]:null==e||t?n[0]:h.initial(n,n.length-e)},h.initial=function(n,e,t){return a.call(n,0,Math.max(0,n.length-(null==e||t?1:e)))},h.last=function(n,e,t){return null==n||n.length<1?null==e?void 0:[]:null==e||t?n[n.length-1]:h.rest(n,Math.max(0,n.length-e))},h.rest=h.tail=h.drop=function(n,e,t){return a.call(n,null==e||t?1:e)},h.compact=function(n){return h.filter(n,Boolean)};var T=function(n,e,t,r){for(var i=(r=r||[]).length,u=0,o=j(n);u<o;u++){var c=n[u];if(x(c)&&(h.isArray(c)||h.isArguments(c)))if(e)for(var a=0,l=c.length;a<l;)r[i++]=c[a++];else T(c,e,t,r),i=r.length;else t||(r[i++]=c)}return r};h.flatten=function(n,e){return T(n,e,!1)},h.without=g(function(n,e){return h.difference(n,e)}),h.uniq=h.unique=function(n,e,t,r){h.isBoolean(e)||(r=t,t=e,e=!1),null!=t&&(t=y(t,r));for(var i=[],u=[],o=0,c=j(n);o<c;o++){var a=n[o],l=t?t(a,o,n):a;e&&!t?(o&&u===l||i.push(a),u=l):t?h.contains(u,l)||(u.push(l),i.push(a)):h.contains(i,a)||i.push(a)}return i},h.union=g(function(n){return h.uniq(T(n,!0,!0))}),h.intersection=function(n){for(var e=[],t=arguments.length,r=0,i=j(n);r<i;r++){var u=n[r];if(!h.contains(e,u)){var o;for(o=1;o<t&&h.contains(arguments[o],u);o++);o===t&&e.push(u)}}return e},h.difference=g(function(n,e){return e=T(e,!0,!0),h.filter(n,function(n){return!h.contains(e,n)})}),h.unzip=function(n){for(var e=n&&h.max(n,j).length||0,t=Array(e),r=0;r<e;r++)t[r]=h.pluck(n,r);return t},h.zip=g(h.unzip),h.object=function(n,e){for(var t={},r=0,i=j(n);r<i;r++)e?t[n[r]]=e[r]:t[n[r][0]]=n[r][1];return t};function O(u){return function(n,e,t){e=y(e,t);for(var r=j(n),i=0<u?0:r-1;0<=i&&i<r;i+=u)if(e(n[i],i,n))return i;return-1}}h.findIndex=O(1),h.findLastIndex=O(-1),h.sortedIndex=function(n,e,t,r){for(var i=(t=y(t,r,1))(e),u=0,o=j(n);u<o;){var c=Math.floor((u+o)/2);t(n[c])<i?u=c+1:o=c}return u};function C(u,o,c){return function(n,e,t){var r=0,i=j(n);if("number"==typeof t)0<u?r=0<=t?t:Math.max(t+i,r):i=0<=t?Math.min(t+1,i):t+i+1;else if(c&&t&&i)return n[t=c(n,e)]===e?t:-1;if(e!=e)return 0<=(t=o(a.call(n,r,i),h.isNaN))?t+r:-1;for(t=0<u?r:i-1;0<=t&&t<i;t+=u)if(n[t]===e)return t;return-1}}h.indexOf=C(1,h.findIndex,h.sortedIndex),h.lastIndexOf=C(-1,h.findLastIndex),h.range=function(n,e,t){null==e&&(e=n||0,n=0),t=t||(e<n?-1:1);for(var r=Math.max(Math.ceil((e-n)/t),0),i=Array(r),u=0;u<r;u++,n+=t)i[u]=n;return i},h.chunk=function(n,e){if(null==e||e<1)return[];for(var t=[],r=0,i=n.length;r<i;)t.push(a.call(n,r,r+=e));return t};function I(n,e,t,r,i){if(!(r instanceof e))return n.apply(t,i);var u=S(n.prototype),o=n.apply(u,i);return h.isObject(o)?o:u}h.bind=g(function(e,t,r){if(!h.isFunction(e))throw new TypeError("Bind must be called on a function");var i=g(function(n){return I(e,i,t,this,r.concat(n))});return i}),h.partial=g(function(i,u){var o=h.partial.placeholder,c=function(){for(var n=0,e=u.length,t=Array(e),r=0;r<e;r++)t[r]=u[r]===o?arguments[n++]:u[r];for(;n<arguments.length;)t.push(arguments[n++]);return I(i,c,this,this,t)};return c}),(h.partial.placeholder=h).bindAll=g(function(n,e){var t=(e=T(e,!1,!1)).length;if(t<1)throw new Error("bindAll must be passed function names");for(;t--;){var r=e[t];n[r]=h.bind(n[r],n)}}),h.memoize=function(r,i){var u=function(n){var e=u.cache,t=""+(i?i.apply(this,arguments):n);return _(e,t)||(e[t]=r.apply(this,arguments)),e[t]};return u.cache={},u},h.delay=g(function(n,e,t){return setTimeout(function(){return n.apply(null,t)},e)}),h.defer=h.partial(h.delay,h,1),h.throttle=function(t,r,i){var u,o,c,a,l=0;i=i||{};function s(){l=!1===i.leading?0:h.now(),u=null,a=t.apply(o,c),u||(o=c=null)}function n(){var n=h.now();l||!1!==i.leading||(l=n);var e=r-(n-l);return o=this,c=arguments,e<=0||r<e?(u&&(clearTimeout(u),u=null),l=n,a=t.apply(o,c),u||(o=c=null)):u||!1===i.trailing||(u=setTimeout(s,e)),a}return n.cancel=function(){clearTimeout(u),l=0,u=o=c=null},n},h.debounce=function(t,r,i){function u(n,e){o=null,e&&(c=t.apply(n,e))}var o,c,n=g(function(n){if(o&&clearTimeout(o),i){var e=!o;o=setTimeout(u,r),e&&(c=t.apply(this,n))}else o=h.delay(u,r,this,n);return c});return n.cancel=function(){clearTimeout(o),o=null},n},h.wrap=function(n,e){return h.partial(e,n)},h.negate=function(n){return function(){return!n.apply(this,arguments)}},h.compose=function(){var t=arguments,r=t.length-1;return function(){for(var n=r,e=t[r].apply(this,arguments);n--;)e=t[n].call(this,e);return e}},h.after=function(n,e){return function(){if(--n<1)return e.apply(this,arguments)}},h.before=function(n,e){var t;return function(){return 0<--n&&(t=e.apply(this,arguments)),n<=1&&(e=null),t}},h.once=h.partial(h.before,2),h.restArguments=g;function F(n,e){var t=M.length,r=n.constructor,i=h.isFunction(r)&&r.prototype||o,u="constructor";for(_(n,u)&&!h.contains(e,u)&&e.push(u);t--;)(u=M[t])in n&&n[u]!==i[u]&&!h.contains(e,u)&&e.push(u)}var q=!{toString:null}.propertyIsEnumerable("toString"),M=["valueOf","isPrototypeOf","toString","propertyIsEnumerable","hasOwnProperty","toLocaleString"];h.keys=function(n){if(!h.isObject(n))return[];if(l)return l(n);var e=[];for(var t in n)_(n,t)&&e.push(t);return q&&F(n,e),e},h.allKeys=function(n){if(!h.isObject(n))return[];var e=[];for(var t in n)e.push(t);return q&&F(n,e),e},h.values=function(n){for(var e=h.keys(n),t=e.length,r=Array(t),i=0;i<t;i++)r[i]=n[e[i]];return r},h.mapObject=function(n,e,t){e=y(e,t);for(var r=h.keys(n),i=r.length,u={},o=0;o<i;o++){var c=r[o];u[c]=e(n[c],c,n)}return u},h.pairs=function(n){for(var e=h.keys(n),t=e.length,r=Array(t),i=0;i<t;i++)r[i]=[e[i],n[e[i]]];return r},h.invert=function(n){for(var e={},t=h.keys(n),r=0,i=t.length;r<i;r++)e[n[t[r]]]=t[r];return e},h.functions=h.methods=function(n){var e=[];for(var t in n)h.isFunction(n[t])&&e.push(t);return e.sort()};function N(a,l){return function(n){var e=arguments.length;if(l&&(n=Object(n)),e<2||null==n)return n;for(var t=1;t<e;t++)for(var r=arguments[t],i=a(r),u=i.length,o=0;o<u;o++){var c=i[o];l&&void 0!==n[c]||(n[c]=r[c])}return n}}h.extend=N(h.allKeys),h.extendOwn=h.assign=N(h.keys),h.findKey=function(n,e,t){e=y(e,t);for(var r,i=h.keys(n),u=0,o=i.length;u<o;u++)if(e(n[r=i[u]],r,n))return r};function R(n,e,t){return e in t}var Q,L;h.pick=g(function(n,e){var t={},r=e[0];if(null==n)return t;h.isFunction(r)?(1<e.length&&(r=d(r,e[1])),e=h.allKeys(n)):(r=R,e=T(e,!1,!1),n=Object(n));for(var i=0,u=e.length;i<u;i++){var o=e[i],c=n[o];r(c,o,n)&&(t[o]=c)}return t}),h.omit=g(function(n,t){var e,r=t[0];return h.isFunction(r)?(r=h.negate(r),1<t.length&&(e=t[1])):(t=h.map(T(t,!1,!1),String),r=function(n,e){return!h.contains(t,e)}),h.pick(n,r,e)}),h.defaults=N(h.allKeys,!0),h.create=function(n,e){var t=S(n);return e&&h.extendOwn(t,e),t},h.clone=function(n){return h.isObject(n)?h.isArray(n)?n.slice():h.extend({},n):n},h.tap=function(n,e){return e(n),n},h.isMatch=function(n,e){var t=h.keys(e),r=t.length;if(null==n)return!r;for(var i=Object(n),u=0;u<r;u++){var o=t[u];if(e[o]!==i[o]||!(o in i))return!1}return!0},Q=function(n,e,t,r){if(n===e)return 0!==n||1/n==1/e;if(null==n||null==e)return!1;if(n!=n)return e!=e;var i=typeof n;return("function"==i||"object"==i||"object"==typeof e)&&L(n,e,t,r)},L=function(n,e,t,r){n instanceof h&&(n=n._wrapped),e instanceof h&&(e=e._wrapped);var i=p.call(n);if(i!==p.call(e))return!1;switch(i){case"[object RegExp]":case"[object String]":return""+n==""+e;case"[object Number]":return+n!=+n?+e!=+e:0==+n?1/+n==1/e:+n==+e;case"[object Date]":case"[object Boolean]":return+n==+e;case"[object Symbol]":return f.valueOf.call(n)===f.valueOf.call(e)}var u="[object Array]"===i;if(!u){if("object"!=typeof n||"object"!=typeof e)return!1;var o=n.constructor,c=e.constructor;if(o!==c&&!(h.isFunction(o)&&o instanceof o&&h.isFunction(c)&&c instanceof c)&&"constructor"in n&&"constructor"in e)return!1}r=r||[];for(var a=(t=t||[]).length;a--;)if(t[a]===n)return r[a]===e;if(t.push(n),r.push(e),u){if((a=n.length)!==e.length)return!1;for(;a--;)if(!Q(n[a],e[a],t,r))return!1}else{var l,s=h.keys(n);if(a=s.length,h.keys(e).length!==a)return!1;for(;a--;)if(l=s[a],!_(e,l)||!Q(n[l],e[l],t,r))return!1}return t.pop(),r.pop(),!0},h.isEqual=function(n,e){return Q(n,e)},h.isEmpty=function(n){return null==n||(x(n)&&(h.isArray(n)||h.isString(n)||h.isArguments(n))?0===n.length:0===h.keys(n).length)},h.isElement=function(n){return!(!n||1!==n.nodeType)},h.isArray=c||function(n){return"[object Array]"===p.call(n)},h.isObject=function(n){var e=typeof n;return"function"==e||"object"==e&&!!n},h.each(["Arguments","Function","String","Number","Date","RegExp","Error","Symbol","Map","WeakMap","Set","WeakSet"],function(e){h["is"+e]=function(n){return p.call(n)==="[object "+e+"]"}}),h.isArguments(arguments)||(h.isArguments=function(n){return _(n,"callee")});var U=n.document&&n.document.childNodes;"function"!=typeof/./&&"object"!=typeof Int8Array&&"function"!=typeof U&&(h.isFunction=function(n){return"function"==typeof n||!1}),h.isFinite=function(n){return!h.isSymbol(n)&&isFinite(n)&&!isNaN(parseFloat(n))},h.isNaN=function(n){return h.isNumber(n)&&isNaN(n)},h.isBoolean=function(n){return!0===n||!1===n||"[object Boolean]"===p.call(n)},h.isNull=function(n){return null===n},h.isUndefined=function(n){return void 0===n},h.has=function(n,e){if(!h.isArray(e))return _(n,e);for(var t=e.length,r=0;r<t;r++){var i=e[r];if(null==n||!u.call(n,i))return!1;n=n[i]}return!!t},h.noConflict=function(){return n._=e,this},h.identity=function(n){return n},h.constant=function(n){return function(){return n}},h.noop=function(){},h.property=function(e){return h.isArray(e)?function(n){return b(n,e)}:m(e)},h.propertyOf=function(e){return null==e?function(){}:function(n){return h.isArray(n)?b(e,n):e[n]}},h.matcher=h.matches=function(e){return e=h.extendOwn({},e),function(n){return h.isMatch(n,e)}},h.times=function(n,e,t){var r=Array(Math.max(0,n));e=d(e,t,1);for(var i=0;i<n;i++)r[i]=e(i);return r},h.random=function(n,e){return null==e&&(e=n,n=0),n+Math.floor(Math.random()*(e-n+1))},h.now=Date.now||function(){return(new Date).getTime()};function D(e){function t(n){return e[n]}var n="(?:"+h.keys(e).join("|")+")",r=RegExp(n),i=RegExp(n,"g");return function(n){return n=null==n?"":""+n,r.test(n)?n.replace(i,t):n}}var P={"&":"&","<":"<",">":">",'"':""","'":"'","`":"`"},W=h.invert(P);h.escape=D(P),h.unescape=D(W),h.result=function(n,e,t){h.isArray(e)||(e=[e]);var r=e.length;if(!r)return h.isFunction(t)?t.call(n):t;for(var i=0;i<r;i++){var u=null==n?void 0:n[e[i]];void 0===u&&(u=t,i=r),n=h.isFunction(u)?u.call(n):u}return n};var B=0;h.uniqueId=function(n){var e=++B+"";return n?n+e:e},h.templateSettings={evaluate:/<%([\s\S]+?)%>/g,interpolate:/<%=([\s\S]+?)%>/g,escape:/<%-([\s\S]+?)%>/g};function Y(n){return"\\"+K[n]}var z=/(.)^/,K={"'":"'","\\":"\\","\r":"r","\n":"n","\u2028":"u2028","\u2029":"u2029"},G=/\\|'|\r|\n|\u2028|\u2029/g;h.template=function(u,n,e){!n&&e&&(n=e),n=h.defaults({},n,h.templateSettings);var t,r=RegExp([(n.escape||z).source,(n.interpolate||z).source,(n.evaluate||z).source].join("|")+"|$","g"),o=0,c="__p+='";u.replace(r,function(n,e,t,r,i){return c+=u.slice(o,i).replace(G,Y),o=i+n.length,e?c+="'+\n((__t=("+e+"))==null?'':_.escape(__t))+\n'":t?c+="'+\n((__t=("+t+"))==null?'':__t)+\n'":r&&(c+="';\n"+r+"\n__p+='"),n}),c+="';\n",n.variable||(c="with(obj||{}){\n"+c+"}\n"),c="var __t,__p='',__j=Array.prototype.join,print=function(){__p+=__j.call(arguments,'');};\n"+c+"return __p;\n";try{t=new Function(n.variable||"obj","_",c)}catch(n){throw n.source=c,n}function i(n){return t.call(this,n,h)}var a=n.variable||"obj";return i.source="function("+a+"){\n"+c+"}",i},h.chain=function(n){var e=h(n);return e._chain=!0,e};function H(n,e){return n._chain?h(e).chain():e}h.mixin=function(t){return h.each(h.functions(t),function(n){var e=h[n]=t[n];h.prototype[n]=function(){var n=[this._wrapped];return i.apply(n,arguments),H(this,e.apply(h,n))}}),h},h.mixin(h),h.each(["pop","push","reverse","shift","sort","splice","unshift"],function(e){var t=r[e];h.prototype[e]=function(){var n=this._wrapped;return t.apply(n,arguments),"shift"!==e&&"splice"!==e||0!==n.length||delete n[0],H(this,n)}}),h.each(["concat","join","slice"],function(n){var e=r[n];h.prototype[n]=function(){return H(this,e.apply(this._wrapped,arguments))}}),h.prototype.value=function(){return this._wrapped},h.prototype.valueOf=h.prototype.toJSON=h.prototype.value,h.prototype.toString=function(){return String(this._wrapped)},"function"==typeof define&&define.amd&&define("underscore",[],function(){return h})}()}).call(this,"undefined"!=typeof global?global:"undefined"!=typeof self?self:"undefined"!=typeof window?window:{})},{}],3:[function(n,e,t){"use strict";var i=n("underscore"),u=n("../events");e.exports=function(n){var r=n.getComponent("MenuItem");return n.extend(r,{constructor:function(n,e){var t=e.source;if(!i.isObject(t))throw new Error('was not provided a "source" object, but rather: '+typeof t);e=i.extend({selectable:!0,label:t.label},e),r.call(this,n,e),this.source=t},handleClick:function(n){r.prototype.handleClick.call(this,n),this.player().trigger(u.QUALITY_REQUESTED,this.source)}})}},{"../events":5,underscore:2}],4:[function(n,e,t){"use strict";var i=n("underscore"),u=n("../events"),o=n("./QualityOption"),c="vjs-quality-changing";e.exports=function(n){var e,r=n.getComponent("MenuButton"),t=o(n);return e=n.extend(r,{constructor:function(t,n){r.call(this,t,n),t.on(u.QUALITY_REQUESTED,function(n,e){this.setSelectedSource(e),t.addClass(c),t.one("loadeddata",function(){t.removeClass(c)})}.bind(this)),t.on(u.PLAYER_SOURCES_CHANGED,function(){this.update()}.bind(this)),t.on(u.QUALITY_SELECTED,function(n,e){this.setSelectedSource(e)}.bind(this)),t.one("ready",function(){this.selectedSrc=t.src(),this.update()}.bind(this)),this.controlText("Open quality selector menu")},setSelectedSource:function(n){var e=n?n.src:void 0;this.selectedSrc!==e&&(this.selectedSrc=e,i.each(this.items,function(n){n.selected(n.source.src===e)}))},createItems:function(){var e=this.player(),n=e.currentSources();return n=n.filter(function(n){return null==n.hidequalityoption}),i.map(n,function(n){return new t(e,{source:n,selected:n.src===this.selectedSrc})}.bind(this))},buildWrapperCSSClass:function(){return"vjs-quality-selector "+r.prototype.buildWrapperCSSClass.call(this)}}),n.registerComponent("QualitySelector",e),e}},{"../events":5,"./QualityOption":3,underscore:2}],5:[function(n,e,t){"use strict";e.exports={QUALITY_REQUESTED:"qualityRequested",QUALITY_SELECTED:"qualitySelected",PLAYER_SOURCES_CHANGED:"playerSourcesChanged"}},{}],6:[function(n,e,t){"use strict";var c=n("underscore"),r=n("./events"),i=n("./components/QualitySelector"),u=n("./middleware/SourceInterceptor"),a=n("./util/SafeSeek");e.exports=function(n){n=n||window.videojs,i(n),u(n),n.hook("setup",function(o){o.on(r.QUALITY_REQUESTED,function(n,e){var t=o.currentSources(),r=o.currentTime(),i=o.playbackRate(),u=o.paused();c.each(t,function(n){n.selected=!1}),c.findWhere(t,{src:e.src}).selected=!0,o._qualitySelectorSafeSeek&&o._qualitySelectorSafeSeek.onQualitySelectionChange(),o.src(t),o.ready(function(){o._qualitySelectorSafeSeek&&!o._qualitySelectorSafeSeek.hasFinished()||(o._qualitySelectorSafeSeek=new a(o,r),o.playbackRate(i)),u||o.play()})})})},e.exports.EVENTS=r},{"./components/QualitySelector":4,"./events":5,"./middleware/SourceInterceptor":7,"./util/SafeSeek":9,underscore:2}],7:[function(n,e,t){"use strict";var u=n("underscore"),o=n("../events");e.exports=function(n){n.use("*",function(i){return{setSource:function(n,e){var t,r=i.currentSources();i._qualitySelectorSafeSeek&&i._qualitySelectorSafeSeek.onPlayerSourcesChange(),u.isEqual(r,i._qualitySelectorPreviousSources)||(i.trigger(o.PLAYER_SOURCES_CHANGED,r),i._qualitySelectorPreviousSources=r),t=u.find(r,function(n){return!0===n.selected||"true"===n.selected||"selected"===n.selected})||n,i.trigger(o.QUALITY_SELECTED,t),e(null,t)}}})}},{"../events":5,underscore:2}],8:[function(n,e,t){"use strict";n("./index")()},{"./index":6}],9:[function(n,e,t){"use strict";var r=n("class.extend");e.exports=r.extend({init:function(n,e){this._player=n,this._seekToTime=e,this._hasFinished=!1,this._keepThisInstanceWhenPlayerSourcesChange=!1,this._seekWhenSafe()},_seekWhenSafe:function(){this._player.readyState()<3?(this._seekFn=this._seek.bind(this),this._player.one("canplay",this._seekFn)):this._seek()},onPlayerSourcesChange:function(){this._keepThisInstanceWhenPlayerSourcesChange?this._keepThisInstanceWhenPlayerSourcesChange=!1:this.cancel()},onQualitySelectionChange:function(){this.hasFinished()||(this._keepThisInstanceWhenPlayerSourcesChange=!0)},_seek:function(){this._player.currentTime(this._seekToTime),this._keepThisInstanceWhenPlayerSourcesChange=!1,this._hasFinished=!0},hasFinished:function(){return this._hasFinished},cancel:function(){this._player.off("canplay",this._seekFn),this._keepThisInstanceWhenPlayerSourcesChange=!1,this._hasFinished=!0}})},{"class.extend":1}]},{},[8]); //# sourceMappingURL=silvermine-videojs-quality-selector.min.js.map
\ No newline at end of file diff --git a/assets/js/subscribe_widget.js b/assets/js/subscribe_widget.js index 216c36fe..45ff5706 100644 --- a/assets/js/subscribe_widget.js +++ b/assets/js/subscribe_widget.js @@ -1,4 +1,5 @@ -var subscribe_data = JSON.parse(document.getElementById('subscribe_data').innerHTML); +'use strict'; +var subscribe_data = JSON.parse(document.getElementById('subscribe_data').textContent); var subscribe_button = document.getElementById('subscribe'); subscribe_button.parentNode['action'] = 'javascript:void(0)'; @@ -9,9 +10,11 @@ if (subscribe_button.getAttribute('data-type') === 'subscribe') { subscribe_button.onclick = unsubscribe; } -function subscribe(retries = 5) { +function subscribe(retries) { + if (retries === undefined) retries = 5; + if (retries <= 0) { - console.log('Failed to subscribe.'); + console.warn('Failed to subscribe.'); return; } @@ -28,30 +31,33 @@ function subscribe(retries = 5) { subscribe_button.innerHTML = '<b>' + subscribe_data.unsubscribe_text + ' | ' + subscribe_data.sub_count_text + '</b>'; xhr.onreadystatechange = function () { - if (xhr.readyState == 4) { - if (xhr.status != 200) { + if (xhr.readyState === 4) { + if (xhr.status !== 200) { subscribe_button.onclick = subscribe; subscribe_button.innerHTML = fallback; } } - } + }; xhr.onerror = function () { - console.log('Subscribing failed... ' + retries + '/5'); - setTimeout(function () { subscribe(retries - 1) }, 1000); - } + console.warn('Subscribing failed... ' + retries + '/5'); + setTimeout(function () { subscribe(retries - 1); }, 1000); + }; xhr.ontimeout = function () { - console.log('Subscribing failed... ' + retries + '/5'); + console.warn('Subscribing failed... ' + retries + '/5'); subscribe(retries - 1); - } + }; xhr.send('csrf_token=' + subscribe_data.csrf_token); } -function unsubscribe(retries = 5) { +function unsubscribe(retries) { + if (retries === undefined) + retries = 5; + if (retries <= 0) { - console.log('Failed to subscribe'); + console.warn('Failed to subscribe'); return; } @@ -68,23 +74,23 @@ function unsubscribe(retries = 5) { subscribe_button.innerHTML = '<b>' + subscribe_data.subscribe_text + ' | ' + subscribe_data.sub_count_text + '</b>'; xhr.onreadystatechange = function () { - if (xhr.readyState == 4) { - if (xhr.status != 200) { + if (xhr.readyState === 4) { + if (xhr.status !== 200) { subscribe_button.onclick = unsubscribe; subscribe_button.innerHTML = fallback; } } - } + }; xhr.onerror = function () { - console.log('Unsubscribing failed... ' + retries + '/5'); - setTimeout(function () { unsubscribe(retries - 1) }, 1000); - } + console.warn('Unsubscribing failed... ' + retries + '/5'); + setTimeout(function () { unsubscribe(retries - 1); }, 1000); + }; xhr.ontimeout = function () { - console.log('Unsubscribing failed... ' + retries + '/5'); + console.warn('Unsubscribing failed... ' + retries + '/5'); unsubscribe(retries - 1); - } + }; xhr.send('csrf_token=' + subscribe_data.csrf_token); } diff --git a/assets/js/themes.js b/assets/js/themes.js index 0214a7f0..3f503b38 100644 --- a/assets/js/themes.js +++ b/assets/js/themes.js @@ -1,8 +1,9 @@ +'use strict'; var toggle_theme = document.getElementById('toggle_theme'); toggle_theme.href = 'javascript:void(0);'; toggle_theme.addEventListener('click', function () { - var dark_mode = document.body.classList.contains("light-theme"); + var dark_mode = document.body.classList.contains('light-theme'); var url = '/toggle_theme?redirect=false'; var xhr = new XMLHttpRequest(); @@ -13,7 +14,7 @@ toggle_theme.addEventListener('click', function () { set_mode(dark_mode); try { window.localStorage.setItem('dark_mode', dark_mode ? 'dark' : 'light'); - } catch {} + } catch (e) {} xhr.send(); }); @@ -29,7 +30,7 @@ window.addEventListener('DOMContentLoaded', function () { try { // Update localStorage if dark mode preference changed on preferences page window.localStorage.setItem('dark_mode', dark_mode); - } catch {} + } catch (e) {} update_mode(dark_mode); }); @@ -46,11 +47,11 @@ function scheme_switch (e) { if (localStorage.getItem('dark_mode')) { return; } - } catch {} + } catch (exception) {} if (e.matches) { - if (e.media.includes("dark")) { + if (e.media.includes('dark')) { set_mode(true); - } else if (e.media.includes("light")) { + } else if (e.media.includes('light')) { set_mode(false); } } @@ -77,15 +78,13 @@ function update_mode (mode) { // If preference for dark mode indicated set_mode(true); } - else if (mode === 'false' /* for backwards compatibility */ || mode === 'light') { - // If preference for light mode indicated - set_mode(false); - } + else if (mode === 'false' /* for backwards compatibility */ || mode === 'light') { + // If preference for light mode indicated + set_mode(false); + } else if (document.getElementById('dark_mode_pref').textContent === '' && window.matchMedia('(prefers-color-scheme: dark)').matches) { // If no preference indicated here and no preference indicated on the preferences page (backend), but the browser tells us that the operating system has a dark theme set_mode(true); } // else do nothing, falling back to the mode defined by the `dark_mode` preference on the preferences page (backend) } - - diff --git a/assets/js/watch.js b/assets/js/watch.js index 1579abf4..29d58be5 100644 --- a/assets/js/watch.js +++ b/assets/js/watch.js @@ -1,31 +1,32 @@ -var video_data = JSON.parse(document.getElementById('video_data').innerHTML); +'use strict'; +var video_data = JSON.parse(document.getElementById('video_data').textContent); String.prototype.supplant = function (o) { return this.replace(/{([^{}]*)}/g, function (a, b) { var r = o[b]; return typeof r === 'string' || typeof r === 'number' ? r : a; }); -} +}; function toggle_parent(target) { - body = target.parentNode.parentNode.children[1]; + var body = target.parentNode.parentNode.children[1]; if (body.style.display === null || body.style.display === '') { - target.innerHTML = '[ + ]'; + target.textContent = '[ + ]'; body.style.display = 'none'; } else { - target.innerHTML = '[ - ]'; + target.textContent = '[ − ]'; body.style.display = ''; } } function toggle_comments(event) { var target = event.target; - body = target.parentNode.parentNode.parentNode.children[1]; + var body = target.parentNode.parentNode.parentNode.children[1]; if (body.style.display === null || body.style.display === '') { - target.innerHTML = '[ + ]'; + target.textContent = '[ + ]'; body.style.display = 'none'; } else { - target.innerHTML = '[ - ]'; + target.textContent = '[ − ]'; body.style.display = ''; } } @@ -43,13 +44,13 @@ function swap_comments(event) { function hide_youtube_replies(event) { var target = event.target; - sub_text = target.getAttribute('data-inner-text'); - inner_text = target.getAttribute('data-sub-text'); + var sub_text = target.getAttribute('data-inner-text'); + var inner_text = target.getAttribute('data-sub-text'); - body = target.parentNode.parentNode.children[1]; + var body = target.parentNode.parentNode.children[1]; body.style.display = 'none'; - target.innerHTML = sub_text; + target.textContent = sub_text; target.onclick = show_youtube_replies; target.setAttribute('data-inner-text', inner_text); target.setAttribute('data-sub-text', sub_text); @@ -58,13 +59,13 @@ function hide_youtube_replies(event) { function show_youtube_replies(event) { var target = event.target; - sub_text = target.getAttribute('data-inner-text'); - inner_text = target.getAttribute('data-sub-text'); + var sub_text = target.getAttribute('data-inner-text'); + var inner_text = target.getAttribute('data-sub-text'); - body = target.parentNode.parentNode.children[1]; + var body = target.parentNode.parentNode.children[1]; body.style.display = ''; - target.innerHTML = sub_text; + target.textContent = sub_text; target.onclick = hide_youtube_replies; target.setAttribute('data-inner-text', inner_text); target.setAttribute('data-sub-text', sub_text); @@ -116,25 +117,26 @@ function number_with_separator(val) { } function get_playlist(plid, retries) { - if (retries == undefined) retries = 5; - playlist = document.getElementById('playlist'); + if (retries === undefined) retries = 5; + var playlist = document.getElementById('playlist'); if (retries <= 0) { - console.log('Failed to pull playlist'); + console.warn('Failed to pull playlist'); playlist.innerHTML = ''; return; } playlist.innerHTML = ' \ <h3 style="text-align:center"><div class="loading"><i class="icon ion-ios-refresh"></i></div></h3> \ - <hr>' + <hr>'; + var plid_url; if (plid.startsWith('RD')) { - var plid_url = '/api/v1/mixes/' + plid + + plid_url = '/api/v1/mixes/' + plid + '?continuation=' + video_data.id + '&format=html&hl=' + video_data.preferences.locale; } else { - var plid_url = '/api/v1/playlists/' + plid + + plid_url = '/api/v1/playlists/' + plid + '?index=' + video_data.index + '&continuation=' + video_data.id + '&format=html&hl=' + video_data.preferences.locale; @@ -146,8 +148,8 @@ function get_playlist(plid, retries) { xhr.open('GET', plid_url, true); xhr.onreadystatechange = function () { - if (xhr.readyState == 4) { - if (xhr.status == 200) { + if (xhr.readyState === 4) { + if (xhr.status === 200) { playlist.innerHTML = xhr.response.playlistHtml; var nextVideo = document.getElementById(xhr.response.nextVideo); nextVideo.parentNode.parentNode.scrollTop = nextVideo.offsetTop; @@ -185,35 +187,35 @@ function get_playlist(plid, retries) { document.getElementById('continue').style.display = ''; } } - } + }; xhr.onerror = function () { playlist = document.getElementById('playlist'); playlist.innerHTML = '<h3 style="text-align:center"><div class="loading"><i class="icon ion-ios-refresh"></i></div></h3><hr>'; - console.log('Pulling playlist timed out... ' + retries + '/5'); - setTimeout(function () { get_playlist(plid, retries - 1) }, 1000); - } + console.warn('Pulling playlist timed out... ' + retries + '/5'); + setTimeout(function () { get_playlist(plid, retries - 1); }, 1000); + }; xhr.ontimeout = function () { playlist = document.getElementById('playlist'); playlist.innerHTML = '<h3 style="text-align:center"><div class="loading"><i class="icon ion-ios-refresh"></i></div></h3><hr>'; - console.log('Pulling playlist timed out... ' + retries + '/5'); + console.warn('Pulling playlist timed out... ' + retries + '/5'); get_playlist(plid, retries - 1); - } + }; xhr.send(); } function get_reddit_comments(retries) { - if (retries == undefined) retries = 5; - comments = document.getElementById('comments'); + if (retries === undefined) retries = 5; + var comments = document.getElementById('comments'); if (retries <= 0) { - console.log('Failed to pull comments'); + console.warn('Failed to pull comments'); comments.innerHTML = ''; return; } @@ -231,12 +233,12 @@ function get_reddit_comments(retries) { xhr.open('GET', url, true); xhr.onreadystatechange = function () { - if (xhr.readyState == 4) { - if (xhr.status == 200) { + if (xhr.readyState === 4) { + if (xhr.status === 200) { comments.innerHTML = ' \ <div> \ <h3> \ - <a href="javascript:void(0)">[ - ]</a> \ + <a href="javascript:void(0)">[ − ]</a> \ {title} \ </h3> \ <p> \ @@ -263,34 +265,34 @@ function get_reddit_comments(retries) { comments.children[0].children[1].children[0].onclick = swap_comments; } else { if (video_data.params.comments[1] === 'youtube') { - console.log('Pulling comments failed... ' + retries + '/5'); - setTimeout(function () { get_youtube_comments(retries - 1) }, 1000); + console.warn('Pulling comments failed... ' + retries + '/5'); + setTimeout(function () { get_youtube_comments(retries - 1); }, 1000); } else { comments.innerHTML = fallback; } } } - } + }; xhr.onerror = function () { - console.log('Pulling comments failed... ' + retries + '/5'); - setTimeout(function () { get_reddit_comments(retries - 1) }, 1000); - } + console.warn('Pulling comments failed... ' + retries + '/5'); + setTimeout(function () { get_reddit_comments(retries - 1); }, 1000); + }; xhr.ontimeout = function () { - console.log('Pulling comments failed... ' + retries + '/5'); + console.warn('Pulling comments failed... ' + retries + '/5'); get_reddit_comments(retries - 1); - } + }; xhr.send(); } function get_youtube_comments(retries) { - if (retries == undefined) retries = 5; - comments = document.getElementById('comments'); + if (retries === undefined) retries = 5; + var comments = document.getElementById('comments'); if (retries <= 0) { - console.log('Failed to pull comments'); + console.warn('Failed to pull comments'); comments.innerHTML = ''; return; } @@ -309,12 +311,12 @@ function get_youtube_comments(retries) { xhr.open('GET', url, true); xhr.onreadystatechange = function () { - if (xhr.readyState == 4) { - if (xhr.status == 200) { + if (xhr.readyState === 4) { + if (xhr.status === 200) { comments.innerHTML = ' \ <div> \ <h3> \ - <a href="javascript:void(0)">[ - ]</a> \ + <a href="javascript:void(0)">[ − ]</a> \ {commentsText} \ </h3> \ <b> \ @@ -336,27 +338,27 @@ function get_youtube_comments(retries) { comments.children[0].children[1].children[0].onclick = swap_comments; } else { if (video_data.params.comments[1] === 'youtube') { - setTimeout(function () { get_youtube_comments(retries - 1) }, 1000); + setTimeout(function () { get_youtube_comments(retries - 1); }, 1000); } else { comments.innerHTML = ''; } } } - } + }; xhr.onerror = function () { comments.innerHTML = '<h3 style="text-align:center"><div class="loading"><i class="icon ion-ios-refresh"></i></div></h3>'; - console.log('Pulling comments failed... ' + retries + '/5'); - setTimeout(function () { get_youtube_comments(retries - 1) }, 1000); - } + console.warn('Pulling comments failed... ' + retries + '/5'); + setTimeout(function () { get_youtube_comments(retries - 1); }, 1000); + }; xhr.ontimeout = function () { comments.innerHTML = '<h3 style="text-align:center"><div class="loading"><i class="icon ion-ios-refresh"></i></div></h3>'; - console.log('Pulling comments failed... ' + retries + '/5'); + console.warn('Pulling comments failed... ' + retries + '/5'); get_youtube_comments(retries - 1); - } + }; xhr.send(); } @@ -373,7 +375,7 @@ function get_youtube_replies(target, load_more, load_replies) { '?format=html' + '&hl=' + video_data.preferences.locale + '&thin_mode=' + video_data.preferences.thin_mode + - '&continuation=' + continuation + '&continuation=' + continuation; if (load_replies) { url += '&action=action_get_comment_replies'; } @@ -383,8 +385,8 @@ function get_youtube_replies(target, load_more, load_replies) { xhr.open('GET', url, true); xhr.onreadystatechange = function () { - if (xhr.readyState == 4) { - if (xhr.status == 200) { + if (xhr.readyState === 4) { + if (xhr.status === 200) { if (load_more) { body = body.parentNode.parentNode; body.removeChild(body.lastElementChild); @@ -412,12 +414,12 @@ function get_youtube_replies(target, load_more, load_replies) { body.innerHTML = fallback; } } - } + }; xhr.ontimeout = function () { - console.log('Pulling comments failed.'); + console.warn('Pulling comments failed.'); body.innerHTML = fallback; - } + }; xhr.send(); } @@ -461,7 +463,7 @@ window.addEventListener('load', function (e) { } else if (video_data.params.comments[1] === 'reddit') { get_reddit_comments(); } else { - comments = document.getElementById('comments'); + var comments = document.getElementById('comments'); comments.innerHTML = ''; } }); diff --git a/assets/js/watched_widget.js b/assets/js/watched_widget.js index ba741974..87989a79 100644 --- a/assets/js/watched_widget.js +++ b/assets/js/watched_widget.js @@ -1,4 +1,5 @@ -var watched_data = JSON.parse(document.getElementById('watched_data').innerHTML); +'use strict'; +var watched_data = JSON.parse(document.getElementById('watched_data').textContent); function mark_watched(target) { var tile = target.parentNode.parentNode.parentNode.parentNode.parentNode; @@ -13,12 +14,12 @@ function mark_watched(target) { xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded'); xhr.onreadystatechange = function () { - if (xhr.readyState == 4) { - if (xhr.status != 200) { + if (xhr.readyState === 4) { + if (xhr.status !== 200) { tile.style.display = ''; } } - } + }; xhr.send('csrf_token=' + watched_data.csrf_token); } @@ -26,7 +27,7 @@ function mark_watched(target) { function mark_unwatched(target) { var tile = target.parentNode.parentNode.parentNode.parentNode.parentNode; tile.style.display = 'none'; - var count = document.getElementById('count') + var count = document.getElementById('count'); count.innerText = count.innerText - 1; var url = '/watch_ajax?action_mark_unwatched=1&redirect=false' + @@ -38,13 +39,13 @@ function mark_unwatched(target) { xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded'); xhr.onreadystatechange = function () { - if (xhr.readyState == 4) { - if (xhr.status != 200) { + if (xhr.readyState === 4) { + if (xhr.status !== 200) { count.innerText = count.innerText - 1 + 2; tile.style.display = ''; } } - } + }; xhr.send('csrf_token=' + watched_data.csrf_token); -}
\ No newline at end of file +} diff --git a/docker-compose.yml b/docker-compose.yml index cd1df4ff..fa14a8e8 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -39,7 +39,7 @@ services: - invidious-db invidious-db: - image: docker.io/library/postgres:14 + image: docker.io/library/postgres:13 restart: unless-stopped volumes: - postgresdata:/var/lib/postgresql/data diff --git a/docker/Dockerfile b/docker/Dockerfile index df35a179..1346f6eb 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -1,4 +1,4 @@ -FROM crystallang/crystal:1.2.2-alpine AS builder +FROM crystallang/crystal:1.4.1-alpine AS builder RUN apk add --no-cache sqlite-static yaml-static ARG release diff --git a/docker/Dockerfile.arm64 b/docker/Dockerfile.arm64 index 5f4d3793..75cab819 100644 --- a/docker/Dockerfile.arm64 +++ b/docker/Dockerfile.arm64 @@ -1,5 +1,5 @@ -FROM alpine:3.15 AS builder -RUN apk add --no-cache 'crystal=1.2.2-r0' shards sqlite-static yaml-static yaml-dev libxml2-dev zlib-static openssl-libs-static openssl-dev musl-dev +FROM alpine:edge AS builder +RUN apk add --no-cache 'crystal=1.4.1-r0' shards sqlite-static yaml-static yaml-dev libxml2-dev zlib-static openssl-libs-static openssl-dev musl-dev ARG release @@ -34,7 +34,7 @@ RUN if [ ${release} == 1 ] ; then \ --link-flags "-lxml2 -llzma"; \ fi -FROM alpine:3.15 +FROM alpine:edge RUN apk add --no-cache librsvg ttf-opensans WORKDIR /invidious RUN addgroup -g 1000 -S invidious && \ diff --git a/locales/ar.json b/locales/ar.json index 10ef200b..01c9bbb9 100644 --- a/locales/ar.json +++ b/locales/ar.json @@ -121,7 +121,7 @@ "Subscriptions": "الاشتراكات", "search": "بحث", "Log out": "تسجيل الخروج", - "Released under the AGPLv3 on Github.": "صدر تحت AGPLv3 على Github.", + "Released under the AGPLv3 on Github.": "صدر تحت AGPLv3 على GitHub.", "Source available here.": "الأكواد متوفرة هنا.", "View JavaScript license information.": "مشاهدة معلومات حول تراخيص الجافاسكريبت.", "View privacy policy.": "عرض سياسة الخصوصية.", @@ -141,7 +141,6 @@ "Show less": "عرض اقل", "Watch on YouTube": "مشاهدة الفيديو على اليوتيوب", "Switch Invidious Instance": "تبديل المثيل Invidious", - "Broken? Try another Invidious Instance": "معطل؟ جرب مثيل Invidious آخر", "Hide annotations": "إخفاء الملاحظات في الفيديو", "Show annotations": "عرض الملاحظات في الفيديو", "Genre: ": "النوع: ", @@ -329,39 +328,38 @@ "Videos": "الفيديوهات", "Playlists": "قوائم التشغيل", "Community": "المجتمع", - "relevance": "ملاؤم", - "rating": "تقييم", - "date": "التاريخ", - "views": "مشاهدات", - "content_type": "نوع المحتوى", - "duration": "المدة الزمنية", - "features": "الميزات", - "sort": "فرز", - "hour": "آخر ساعة", - "today": "اليوم", - "week": "هذا الأسبوع", - "month": "هذا الشهر", - "year": "هذه السنة", - "video": "فيديو", - "channel": "قناة", - "playlist": "قائمة التشغيل", - "movie": "فيلم", - "show": "عرض", - "hd": "عالية الدقة", - "subtitles": "ترجمات", - "creative_commons": "المشاع الإبداعي", - "3d": "ثلاثي الأبعاد", - "live": "مباشر", - "4k": "4k", - "location": "الأماكن", - "hdr": "وضع التباين العالي", - "filter": "معامل الفرز", + "search_filters_sort_option_relevance": "ملاؤم", + "search_filters_sort_option_rating": "تقييم", + "search_filters_sort_option_date": "التاريخ", + "search_filters_sort_option_views": "مشاهدات", + "search_filters_type_label": "نوع المحتوى", + "search_filters_duration_label": "المدة الزمنية", + "search_filters_features_label": "الميزات", + "search_filters_sort_label": "فرز", + "search_filters_date_option_hour": "آخر ساعة", + "search_filters_date_option_today": "اليوم", + "search_filters_date_option_week": "هذا الأسبوع", + "search_filters_date_option_month": "هذا الشهر", + "search_filters_date_option_year": "هذه السنة", + "search_filters_type_option_video": "فيديو", + "search_filters_type_option_channel": "قناة", + "search_filters_type_option_playlist": "قائمة التشغيل", + "search_filters_type_option_movie": "فيلم", + "search_filters_type_option_show": "عرض", + "search_filters_features_option_hd": "عالية الدقة", + "search_filters_features_option_subtitles": "ترجمات", + "search_filters_features_option_c_commons": "المشاع الإبداعي", + "search_filters_features_option_three_d": "ثلاثي الأبعاد", + "search_filters_features_option_live": "مباشر", + "search_filters_features_option_four_k": "4k", + "search_filters_features_option_location": "الأماكن", + "search_filters_features_option_hdr": "وضع التباين العالي", "Current version: ": "الإصدار الحالي: ", "next_steps_error_message": "بعد ذلك يجب أن تحاول: ", "next_steps_error_message_refresh": "تحديث", "next_steps_error_message_go_to_youtube": "انتقل إلى يوتيوب", - "short": "قصير (< 4 دقائق)", - "long": "طويل (> 20 دقيقة)", + "search_filters_duration_option_short": "قصير (< 4 دقائق)", + "search_filters_duration_option_long": "طويل (> 20 دقيقة)", "footer_source_code": "شفرة المصدر", "footer_original_source_code": "كود المصدر الأصلي", "footer_modfied_source_code": "شفرة المصدر المعدلة", @@ -386,7 +384,7 @@ "preferences_quality_dash_option_360p": "360p", "preferences_quality_dash_option_240p": "240p", "preferences_quality_dash_option_144p": "144p", - "purchased": "تم شراؤها", + "search_filters_features_option_purchased": "تم شراؤها", "none": "لاشيء", "videoinfo_started_streaming_x_ago": "بدأ البث منذ `x`", "videoinfo_watch_on_youTube": "مشاهدة على يوتيوب", @@ -395,7 +393,7 @@ "user_created_playlists": "'x' إنشاء قوائم التشغيل", "user_saved_playlists": "قوائم التشغيل المحفوظة 'x'", "Video unavailable": "الفيديو غير متوفر", - "360": "360°", + "search_filters_features_option_three_sixty": "360°", "download_subtitles": "ترجمات - 'x' (.vtt)", "invidious": "الخيالي", "preferences_save_player_pos_label": "حفظ موضع التشغيل: ", @@ -459,5 +457,6 @@ "Portuguese (Brazil)": "البرتغالية (البرازيل)", "Russian (auto-generated)": "الروسية (منشأة تلقائيا)", "Spanish (Spain)": "الإسبانية (إسبانيا)", - "crash_page_search_issue": "بحثت عن <a href=\"`x`\"> المشكلات الموجودة على Github </a>" + "crash_page_search_issue": "بحثت عن <a href=\"`x`\"> المشكلات الموجودة على GitHub </a>", + "search_filters_title": "معامل الفرز" } diff --git a/locales/ca.json b/locales/ca.json index 1fa7cc1f..741414d2 100644 --- a/locales/ca.json +++ b/locales/ca.json @@ -52,16 +52,16 @@ "Download": "Descarrega", "Download as: ": "Descarrega com: ", "Videos": "Vídeos", - "content_type": "Tipus", - "duration": "Duració", - "sort": "Ordena per", - "week": "Aquesta setmana", - "month": "Aquest mes", - "year": "Aquest any", - "video": "Vídeo", - "channel": "Canal", - "short": "Curt (< 4 minuts)", - "long": "Llarg (> 20 minuts)", + "search_filters_type_label": "Tipus", + "search_filters_duration_label": "Duració", + "search_filters_sort_label": "Ordena per", + "search_filters_date_option_week": "Aquesta setmana", + "search_filters_date_option_month": "Aquest mes", + "search_filters_date_option_year": "Aquest any", + "search_filters_type_option_video": "Vídeo", + "search_filters_type_option_channel": "Canal", + "search_filters_duration_option_short": "Curt (< 4 minuts)", + "search_filters_duration_option_long": "Llarg (> 20 minuts)", "Current version: ": "Versió actual: ", "Malay": "Malai", "Persian": "Persa", @@ -93,11 +93,11 @@ "Spanish": "Castellà", "Vietnamese": "Vietnamita", "News": "Notícies", - "show": "Mostra", + "search_filters_type_option_show": "Mostra", "footer_documentation": "Documentació", "Thai": "Tailandès", "Music": "Música", - "relevance": "Rellevància", - "hour": "Última hora", - "today": "Avui" + "search_filters_sort_option_relevance": "Rellevància", + "search_filters_date_option_hour": "Última hora", + "search_filters_date_option_today": "Avui" } diff --git a/locales/cs.json b/locales/cs.json index 10dd685e..318866b1 100644 --- a/locales/cs.json +++ b/locales/cs.json @@ -137,7 +137,7 @@ "Show annotations": "Zobrazit poznámky", "Genre: ": "Žánr: ", "License: ": "Licence: ", - "Family friendly? ": "Vhodné pro děti? ", + "Family friendly? ": "Vhodné pro rodiny? ", "Engagement: ": "Zapojení: ", "English": "Angličtina", "English (auto-generated)": "Angličtina (automaticky generováno)", @@ -262,35 +262,34 @@ "Video mode": "Videový režim", "Videos": "Videa", "Community": "Komunita", - "rating": "Hodnocení", - "date": "Datum zveřejnění", - "views": "Počet zhlédnutí", - "duration": "Délka", - "hour": "Před hodinou", - "today": "Dnes", - "week": "Tento týden", - "month": "Tento měsíc", - "year": "Tento rok", - "video": "Video", - "channel": "Kanál", - "playlist": "Playlist", - "movie": "Film", - "show": "Show", - "hd": "HD", - "subtitles": "Titulky", - "creative_commons": "Creative Commons", - "3d": "3D", - "live": "Živě", - "4k": "4K", - "location": "Umístění", - "hdr": "HDR", - "filter": "Filtr", - "generic_count_days_0": "{{count}} den", + "search_filters_sort_option_rating": "Hodnocení", + "search_filters_sort_option_date": "Datum nahrání", + "search_filters_sort_option_views": "Počet zhlédnutí", + "search_filters_duration_label": "Délka", + "search_filters_date_option_hour": "Poslední hodina", + "search_filters_date_option_today": "Dnes", + "search_filters_date_option_week": "Tento týden", + "search_filters_date_option_month": "Tento měsíc", + "search_filters_date_option_year": "Tento rok", + "search_filters_type_option_video": "Video", + "search_filters_type_option_channel": "Kanál", + "search_filters_type_option_playlist": "Playlist", + "search_filters_type_option_movie": "Film", + "search_filters_type_option_show": "Seriál", + "search_filters_features_option_hd": "HD", + "search_filters_features_option_subtitles": "Titulky", + "search_filters_features_option_c_commons": "Creative Commons", + "search_filters_features_option_three_d": "3D", + "search_filters_features_option_live": "Živě", + "search_filters_features_option_four_k": "4K", + "search_filters_features_option_location": "Umístění", + "search_filters_features_option_hdr": "HDR", + "generic_count_days_0": "{{count}} dnem", "generic_count_days_1": "{{count}} dny", - "generic_count_days_2": "{{count}} dní", - "generic_count_hours_0": "{{count}} hodina", - "generic_count_hours_1": "{{count}} hodiny", - "generic_count_hours_2": "{{count}} hodin", + "generic_count_days_2": "{{count}} dny", + "generic_count_hours_0": "{{count}} hodinou", + "generic_count_hours_1": "{{count}} hodinami", + "generic_count_hours_2": "{{count}} hodinami", "crash_page_refresh": "zkusili <a href=\"`x`\">obnovit stránku</a>", "crash_page_switch_instance": "zkusili <a href=\"`x`\">použít jinou instanci</a>", "preferences_vr_mode_label": "Interaktivní 360-stupňová videa (vyžaduje WebGL): ", @@ -302,9 +301,9 @@ "Spanish (auto-generated)": "Španělština (automaticky generováno)", "Spanish (Mexico)": "Španělština (Mexiko)", "Spanish (Spain)": "Španělština (Španělsko)", - "generic_count_years_0": "{{count}} rok", - "generic_count_years_1": "{{count}} roky", - "generic_count_years_2": "{{count}} let", + "generic_count_years_0": "{{count}} rokem", + "generic_count_years_1": "{{count}} lety", + "generic_count_years_2": "{{count}} lety", "Fallback comments: ": "Záložní komentáře: ", "Search": "Hledat", "Top": "Nejlepší", @@ -365,29 +364,24 @@ "Japanese (auto-generated)": "Japonština (automaticky generováno)", "Korean (auto-generated)": "Korejština (automaticky generováno)", "Russian (auto-generated)": "Ruština (automaticky generováno)", - "generic_count_months_0": "{{count}} měsíc", - "generic_count_months_1": "{{count}} měsíce", - "generic_count_months_2": "{{count}} měsíců", - "generic_count_weeks_0": "{{count}} týden", + "generic_count_months_0": "{{count}} měsícem", + "generic_count_months_1": "{{count}} měsíci", + "generic_count_months_2": "{{count}} měsíci", + "generic_count_weeks_0": "{{count}} týdnem", "generic_count_weeks_1": "{{count}} týdny", - "generic_count_weeks_2": "{{count}} týdnů", - "generic_count_minutes_0": "{{count}} minuta", - "generic_count_minutes_1": "{{count}} minuty", - "generic_count_minutes_2": "{{count}} minut", - "short": "Krátké (< 4 minuty)", - "long": "Dlouhé (> 20 minut)", + "generic_count_weeks_2": "{{count}} týdny", + "generic_count_minutes_0": "{{count}} minutou", + "generic_count_minutes_1": "{{count}} minutami", + "generic_count_minutes_2": "{{count}} minutami", "footer_documentation": "Dokumentace", "next_steps_error_message_refresh": "Obnovit stránku", "Chinese": "Čínština", - "360": "360°", "Dutch (auto-generated)": "Nizozemština (automaticky generováno)", "Erroneous token": "Chybný token", "tokens_count_0": "{{count}} token", "tokens_count_1": "{{count}} tokeny", "tokens_count_2": "{{count}} tokenů", "Portuguese (Brazil)": "Portugalština (Brazílie)", - "content_type": "Typ", - "sort": "Řazení", "Token is expired, please try again": "Token vypršel, zkuste to prosím znovu", "English (United States)": "Angličtina (Spojené státy)", "Cantonese (Hong Kong)": "Kantonština (Hong Kong)", @@ -401,7 +395,6 @@ "%A %B %-d, %Y": "%A %B %-d, %Y", "YouTube comment permalink": "Permanentní odkaz YouTube komentáře", "permalink": "permalink", - "purchased": "Zakoupeno", "footer_original_source_code": "Původní zdrojový kód", "adminprefs_modified_source_code_url_label": "URL repozitáře s upraveným zdrojovým kódem", "Video unavailable": "Video není dostupné", @@ -420,9 +413,9 @@ "preferences_quality_dash_option_worst": "Nejhorší", "preferences_quality_dash_option_480p": "480p", "Top enabled: ": "Povoleny nejlepší: ", - "generic_count_seconds_0": "{{count}} sekunda", - "generic_count_seconds_1": "{{count}} sekundy", - "generic_count_seconds_2": "{{count}} sekund", + "generic_count_seconds_0": "{{count}} sekundou", + "generic_count_seconds_1": "{{count}} sekundami", + "generic_count_seconds_2": "{{count}} sekundami", "preferences_save_player_pos_label": "Uložit pozici přehrávání: ", "Incorrect password": "Nesprávné heslo", "View as playlist": "Zobrazit jako playlist", @@ -434,11 +427,9 @@ "Erroneous CAPTCHA": "Chybná CAPTCHA", "Password is a required field": "Heslo je vyžadované pole", "preferences_automatic_instance_redirect_label": "Automatické přesměrování instance (fallback na redirect.invidious.io): ", - "Broken? Try another Invidious Instance": "Je něco rozbité? Zkuste jinou instanci Invidious", "Switch Invidious Instance": "Přepnout instanci Invidious", "Empty playlist": "Prázdný playlist", "footer_source_code": "Zdrojový kód", - "relevance": "Relevantnost", "View YouTube comments": "Zobrazit YouTube komentáře", "Blacklisted regions: ": "Oblasti na černé listině: ", "Wrong username or password": "Nesprávné uživatelské jméno nebo heslo", @@ -454,9 +445,8 @@ "Deleted or invalid channel": "Smazaný nebo neplatný kanál", "This channel does not exist.": "Tento kanál neexistuje.", "Hidden field \"token\" is a required field": "Skryté pole \"token\" je vyžadované", - "features": "Funkce", "Wilson score: ": "Skóre Wilson: ", - "Shared `x`": "Sdíleno `x`", + "Shared `x`": "Zveřejněno `x`", "Premieres in `x`": "Premiéra za `x`", "View `x` comments": { "([^.,0-9]|^)1([^.,0-9]|$)": "Zobrazit `x` komentář", @@ -477,5 +467,24 @@ "Erroneous challenge": "Chybná výzva", "Premieres `x`": "Premiéra `x`", "CAPTCHA is a required field": "CAPTCHA je vyžadované pole", - "`x` ago": "Před `x`" + "`x` ago": "Před `x`", + "search_message_change_filters_or_query": "Zkuste rozšířit vyhledávaný dotaz a/nebo změnit filtry.", + "search_filters_date_option_none": "Jakékoli datum", + "search_filters_date_label": "Datum nahrání", + "search_filters_type_option_all": "Jakýkoli typ", + "search_filters_duration_option_none": "Jakákoli délka", + "search_filters_type_label": "Typ", + "search_filters_duration_option_short": "Krátká (< 4 minuty)", + "search_message_no_results": "Nenalezeny žádné výsledky.", + "search_filters_title": "Filtry", + "search_filters_duration_option_medium": "Střední (4 - 20 minut)", + "search_filters_duration_option_long": "Dlouhá (> 20 minut)", + "search_message_use_another_instance": " Můžete také <a href=\"`x`\">hledat na jiné instanci</a>.", + "search_filters_features_label": "Vlastnosti", + "search_filters_features_option_three_sixty": "360°", + "search_filters_features_option_vr180": "VR180", + "search_filters_features_option_purchased": "Zakoupeno", + "search_filters_sort_label": "Řadit dle", + "search_filters_sort_option_relevance": "Relevantnost", + "search_filters_apply_button": "Použít vybrané filtry" } diff --git a/locales/da.json b/locales/da.json index 92e4e9f9..4816c2c9 100644 --- a/locales/da.json +++ b/locales/da.json @@ -21,15 +21,15 @@ "No": "Nej", "Import and Export Data": "Importer og Eksporter Data", "Import": "Importer", - "Import Invidious data": "Importer Invidious data", - "Import YouTube subscriptions": "Importer YouTube abonnementer", + "Import Invidious data": "Importer Invidious JSON-data", + "Import YouTube subscriptions": "Importer YouTube/OPML-abonnementer", "Import FreeTube subscriptions (.db)": "Importer FreeTube abonnementer (.db)", "Import NewPipe subscriptions (.json)": "Importer NewPipe abonnementer (.json)", "Import NewPipe data (.zip)": "Importer NewPipe data (.zip)", "Export": "Exporter", "Export subscriptions as OPML": "Exporter abonnementer som OPML", "Export subscriptions as OPML (for NewPipe & FreeTube)": "Exporter abonnementer som OPML (til NewPipe & FreeTube)", - "Export data as JSON": "Exporter data som JSON", + "Export data as JSON": "Eksporter Invidious-data som JSON", "Delete account?": "Slet konto?", "History": "Historik", "An alternative front-end to YouTube": "Et alternativt front-end til YouTube", @@ -66,7 +66,7 @@ "preferences_related_videos_label": "Vis relaterede videoer: ", "preferences_annotations_label": "Vis annotationer som standard: ", "preferences_extend_desc_label": "Automatisk udvid videoens beskrivelse: ", - "preferences_vr_mode_label": "Interaktiv 360 graders videoer: ", + "preferences_vr_mode_label": "Interaktive 360 graders videoer (kræver WebGL): ", "preferences_category_visual": "Visuelle præferencer", "preferences_player_style_label": "Afspiller stil: ", "Dark mode: ": "Mørk tilstand: ", @@ -202,7 +202,7 @@ "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", + "search_filters_features_option_live": "Direkte", "Lao": "Lao-tse", "Filipino": "Filippinsk", "Greek": "Græsk", @@ -213,23 +213,22 @@ "preferences_locale_label": "Sprog: ", "News": "Nyheder", "permalink": "permalink", - "date": "Upload dato", - "features": "Funktioner", - "filter": "Filter", + "search_filters_sort_option_date": "Upload dato", + "search_filters_features_label": "Funktioner", "Khmer": "Khmer", "Finnish": "Finsk", - "week": "Denne uge", + "search_filters_date_option_week": "Denne uge", "Korean": "Koreansk", "Telugu": "Telugu", "Malayalam": "Malayalam", "View as playlist": "Se som spilleliste", "Hungarian": "Ungarsk", "Welsh": "Walisisk", - "subtitles": "Undertekster/CC", + "search_filters_features_option_subtitles": "Undertekster/CC", "Bosnian": "Bosnisk", "Yiddish": "Jiddisch", "Belarusian": "Belarussisk", - "today": "I dag", + "search_filters_date_option_today": "I dag", "Shona": "Shona", "Slovenian": "Slovensk", "Gaming": "Gaming", @@ -246,35 +245,35 @@ "footer_documentation": "Dokumentation", "Pashto": "Pashto", "footer_modfied_source_code": "Modificeret Kildekode", - "Released under the AGPLv3 on Github.": "Udgivet under AGPLv3 på Github.", + "Released under the AGPLv3 on Github.": "Udgivet under AGPLv3 på GitHub.", "Tajik": "Tadsjikisk", - "month": "Denne måned", + "search_filters_date_option_month": "Denne måned", "Hebrew": "Hebraisk", "Kannada": "Kannada", "Current version: ": "Nuværende version: ", "Amharic": "Amharisk", "Swedish": "Svensk", "Corsican": "Korsikansk", - "movie": "Film", + "search_filters_type_option_movie": "Film", "Could not pull trending pages.": "Kunne ikke hente trending sider.", "English": "Engelsk", - "hd": "HD", + "search_filters_features_option_hd": "HD", "Hausa": "Islandsk", - "year": "Dette år", + "search_filters_date_option_year": "Dette år", "Japanese": "Japansk", - "content_type": "Type", + "search_filters_type_label": "Type", "Icelandic": "Islandsk", "Basque": "Baskisk", - "rating": "Bedømmelse", + "search_filters_sort_option_rating": "Bedømmelse", "Yoruba": "Yoruba", "Erroneous token": "Fejlagtig token", "Videos": "Videoer", - "show": "Vis", + "search_filters_type_option_show": "Vis", "Luxembourgish": "Luxemboursk", "Vietnamese": "Vietnamesisk", "Latvian": "Lettisk", "Indonesian": "Indonesisk", - "duration": "Varighed", + "search_filters_duration_label": "Varighed", "footer_original_source_code": "Original kildekode", "Search": "Søg", "Serbian": "Serbisk", @@ -289,8 +288,8 @@ "Rating: ": "Bedømmelse: ", "Movies": "Film", "YouTube comment permalink": "Youtube kommentarer permalink", - "location": "Lokation", - "hdr": "HDR", + "search_filters_features_option_location": "Lokation", + "search_filters_features_option_hdr": "HDR", "Cebuano": "Cebuano (Sugbuanon)", "Nyanja": "Nyanja", "Chinese (Simplified)": "Kinesisk (forenklet)", @@ -306,11 +305,11 @@ "German": "Tysk", "Maori": "Maori", "Slovak": "Slovakisk", - "relevance": "Relevans", - "hour": "Sidste time", - "playlist": "Spilleliste", - "long": "Lang (> 20 minutter)", - "creative_commons": "Creative Commons", + "search_filters_sort_option_relevance": "Relevans", + "search_filters_date_option_hour": "Sidste time", + "search_filters_type_option_playlist": "Spilleliste", + "search_filters_duration_option_long": "Lang (> 20 minutter)", + "search_filters_features_option_c_commons": "Creative Commons", "Marathi": "Marathi", "Sindhi": "Sindhi", "preferences_category_misc": "Diverse indstillinger", @@ -327,8 +326,8 @@ "Western Frisian": "Vestfrisisk", "Top": "Top", "Music": "Musik", - "views": "Antal visninger", - "sort": "Sorter efter", + "search_filters_sort_option_views": "Antal visninger", + "search_filters_sort_label": "Sorter efter", "Zulu": "Zulu", "Invidious Private Feed for `x`": "Invidious Privat Feed til `x`", "English (auto-generated)": "Engelsk (autogenereret)", @@ -349,7 +348,6 @@ "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", @@ -359,16 +357,16 @@ "Scottish Gaelic": "Skotsk Gælisk", "Default": "Standard", "Video mode": "Videotilstand", - "short": "Kort (< 4 minutter)", + "search_filters_duration_option_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", + "search_filters_type_option_video": "Video", + "search_filters_type_option_channel": "Kanal", + "search_filters_features_option_three_d": "3D", + "search_filters_features_option_four_k": "4K", "Hmong": "Hmong", "preferences_quality_option_medium": "Medium", "preferences_quality_option_small": "Lille", @@ -381,8 +379,8 @@ "preferences_quality_dash_option_360p": "360p", "preferences_quality_dash_option_144p": "144p", "invidious": "Invidious", - "purchased": "Købt", - "360": "360°", + "search_filters_features_option_purchased": "Købt", + "search_filters_features_option_three_sixty": "360°", "none": "ingen", "videoinfo_started_streaming_x_ago": "Streamen blev startet for `x`siden", "videoinfo_watch_on_youTube": "Se på YouTube", @@ -392,11 +390,75 @@ "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_save_player_pos_label": "Gem afspilningsposition: ", "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" + "preferences_quality_dash_option_240p": "240p", + "subscriptions_unseen_notifs_count": "{{count}} uset notifikation", + "subscriptions_unseen_notifs_count_plural": "{{count}} usete notifikationer", + "comments_view_x_replies": "Vis {{count}} svar", + "comments_view_x_replies_plural": "Vis {{count}} svar", + "comments_points_count": "{{count}} point", + "comments_points_count_plural": "{{count}} point", + "generic_count_years": "{{count}} år", + "generic_count_years_plural": "{{count}} år", + "generic_count_months": "{{count}} måned", + "generic_count_months_plural": "{{count}} måneder", + "generic_count_days": "{{count}} dag", + "generic_count_days_plural": "{{count}} dage", + "generic_count_minutes": "{{count}} minut", + "generic_count_minutes_plural": "{{count}} minutter", + "generic_count_seconds": "{{count}} sekund", + "generic_count_seconds_plural": "{{count}} sekunder", + "generic_subscribers_count": "{{count}} abonnent", + "generic_subscribers_count_plural": "{{count}} abonnenter", + "generic_subscriptions_count": "{{count}} abonnement", + "generic_subscriptions_count_plural": "{{count}} abonnementer", + "generic_videos_count": "{{count}} video", + "generic_videos_count_plural": "{{count}} videoer", + "English (United States)": "Engelsk (USA)", + "French (auto-generated)": "Fransk (autogenereret)", + "Spanish (auto-generated)": "Spansk (autogenereret)", + "crash_page_before_reporting": "Før du rapporterer en fejl, skal du sikre dig, at du har:", + "crash_page_refresh": "forsøgte at <a href=\"`x`\">opdatere siden</a>", + "generic_playlists_count": "{{count}} spilleliste", + "generic_playlists_count_plural": "{{count}} spillelister", + "preferences_watch_history_label": "Aktiver afspilningshistorik: ", + "tokens_count": "{{count}} token", + "tokens_count_plural": "{{count}} tokens", + "Cantonese (Hong Kong)": "Kantonesisk (Hongkong)", + "Chinese": "Kinesisk", + "Chinese (China)": "Kinesisk (Kina)", + "Chinese (Hong Kong)": "Kinesisk (Hongkong)", + "Chinese (Taiwan)": "Kinesisk (Taiwan)", + "Dutch (auto-generated)": "Hollandsk (autogenereret)", + "Indonesian (auto-generated)": "Indonesisk (autogenereret)", + "Interlingue": "Interlingue", + "Japanese (auto-generated)": "Japansk (autogenereret)", + "Korean (auto-generated)": "Koreansk (autogenereret)", + "Russian (auto-generated)": "Russisk (autogenereret)", + "Turkish (auto-generated)": "Tyrkisk (autogenereret)", + "Vietnamese (auto-generated)": "Vietnamesisk (autogenereret)", + "crash_page_report_issue": "Hvis intet af ovenstående hjalp, bedes du <a href=\"`x`\">åbne et nyt problem på GitHub</a> (helst på engelsk) og inkludere følgende tekst i din besked (oversæt IKKE denne tekst):", + "English (United Kingdom)": "Engelsk (Storbritannien)", + "Italian (auto-generated)": "Italiensk (autogenereret)", + "Portuguese (auto-generated)": "Portugisisk (autogenereret)", + "Portuguese (Brazil)": "Portugisisk (Brasilien)", + "generic_views_count": "{{count}} visning", + "generic_views_count_plural": "{{count}} visninger", + "generic_count_hours": "{{count}} time", + "generic_count_hours_plural": "{{count}} timer", + "Spanish (Spain)": "Spansk (Spanien)", + "crash_page_switch_instance": "forsøgte at <a href=\"`x`\">bruge en anden instans</a>", + "German (auto-generated)": "Tysk (autogenereret)", + "Spanish (Mexico)": "Spansk (Mexico)", + "generic_count_weeks": "{{count}} uge", + "generic_count_weeks_plural": "{{count}} uger", + "crash_page_you_found_a_bug": "Det ser ud til, at du har fundet en fejl i Invidious!", + "crash_page_read_the_faq": "læs <a href=\"`x`\">Ofte stillede spørgsmål (FAQ)</a>", + "crash_page_search_issue": "søgte efter <a href=\"`x`\">eksisterende problemer på GitHub</a>", + "search_filters_title": "Filter" } diff --git a/locales/de.json b/locales/de.json index 665810a4..24b83bb3 100644 --- a/locales/de.json +++ b/locales/de.json @@ -21,15 +21,15 @@ "No": "Nein", "Import and Export Data": "Daten importieren und exportieren", "Import": "Importieren", - "Import Invidious data": "Invidious Daten importieren", - "Import YouTube subscriptions": "YouTube Abonnements importieren", + "Import Invidious data": "Invidious-JSON-Daten importieren", + "Import YouTube subscriptions": "YouTube-/OPML-Abonnements importieren", "Import FreeTube subscriptions (.db)": "FreeTube Abonnements importieren (.db)", "Import NewPipe subscriptions (.json)": "NewPipe Abonnements importieren (.json)", "Import NewPipe data (.zip)": "NewPipe Daten importieren (.zip)", "Export": "Exportieren", "Export subscriptions as OPML": "Abonnements als OPML exportieren", "Export subscriptions as OPML (for NewPipe & FreeTube)": "Abonnements als OPML exportieren (für NewPipe & FreeTube)", - "Export data as JSON": "Daten als JSON exportieren", + "Export data as JSON": "Invidious-Daten als JSON exportieren", "Delete account?": "Konto löschen?", "History": "Verlauf", "An alternative front-end to YouTube": "Eine alternative Oberfläche für YouTube", @@ -51,10 +51,10 @@ "preferences_category_player": "Wiedergabeeinstellungen", "preferences_video_loop_label": "Immer wiederholen: ", "preferences_autoplay_label": "Automatisch abspielen: ", - "preferences_continue_label": "Immer automatisch nächstes Video spielen: ", - "preferences_continue_autoplay_label": "nächstes Video automatisch abspielen: ", + "preferences_continue_label": "Immer automatisch nächstes Video abspielen: ", + "preferences_continue_autoplay_label": "Nächstes Video automatisch abspielen: ", "preferences_listen_label": "Nur Ton als Standard: ", - "preferences_local_label": "Proxy-Videos: ", + "preferences_local_label": "Videos durch Proxy leiten: ", "preferences_speed_label": "Standardgeschwindigkeit: ", "preferences_quality_label": "Bevorzugte Videoqualität: ", "preferences_volume_label": "Wiedergabelautstärke: ", @@ -63,12 +63,12 @@ "reddit": "Reddit", "preferences_captions_label": "Standarduntertitel: ", "Fallback captions: ": "Ersatzuntertitel: ", - "preferences_related_videos_label": "Ähnliche Videos anzeigen? ", - "preferences_annotations_label": "Standardmäßig Anmerkungen anzeigen? ", + "preferences_related_videos_label": "Ähnliche Videos anzeigen: ", + "preferences_annotations_label": "Anmerkungen standardmäßig anzeigen: ", "preferences_extend_desc_label": "Videobeschreibung automatisch erweitern: ", - "preferences_vr_mode_label": "Interaktive 360 Grad Videos: ", + "preferences_vr_mode_label": "Interaktive 360-Grad-Videos (erfordert WebGL): ", "preferences_category_visual": "Anzeigeeinstellungen", - "preferences_player_style_label": "Abspielgeräterstil: ", + "preferences_player_style_label": "Player-Stil: ", "Dark mode: ": "Nachtmodus: ", "preferences_dark_mode_label": "Modus: ", "dark": "Nachtmodus", @@ -121,7 +121,7 @@ "Subscriptions": "Abonnements", "search": "Suchen", "Log out": "Abmelden", - "Released under the AGPLv3 on Github.": "Auf Github unter der AGPLv3 Lizenz veröffentlicht.", + "Released under the AGPLv3 on Github.": "Auf GitHub unter der AGPLv3 Lizenz veröffentlicht.", "Source available here.": "Quellcode verfügbar hier.", "View JavaScript license information.": "Javascript Lizenzinformationen anzeigen.", "View privacy policy.": "Datenschutzerklärung einsehen.", @@ -141,7 +141,6 @@ "Show less": "Weniger anzeigen", "Watch on YouTube": "Video auf YouTube ansehen", "Switch Invidious Instance": "Invidious Instanz wechseln", - "Broken? Try another Invidious Instance": "Kaputt? Versuche eine andere Invidious Instanz", "Hide annotations": "Anmerkungen ausblenden", "Show annotations": "Anmerkungen anzeigen", "Genre: ": "Genre: ", @@ -329,45 +328,44 @@ "Videos": "Videos", "Playlists": "Wiedergabelisten", "Community": "Gemeinschaft", - "relevance": "Relevanz", - "rating": "Bewertung", - "date": "Datum", - "views": "Aufrufe", - "content_type": "Inhaltstyp", - "duration": "Dauer", - "features": "Eigenschaften", - "sort": "sortieren", - "hour": "Letzte Stunde", - "today": "Heute", - "week": "Diese Woche", - "month": "Diesen Monat", - "year": "Dieses Jahr", - "video": "Video", - "channel": "Kanal", - "playlist": "Wiedergabeliste", - "movie": "Film", - "show": "anzeigen", - "hd": "HD", - "subtitles": "Untertitel / CC", - "creative_commons": "Creative Commons", - "3d": "3D", - "live": "Live", - "4k": "4K", - "location": "Standort", - "hdr": "HDR", - "filter": "Filtern", + "search_filters_sort_option_relevance": "Relevanz", + "search_filters_sort_option_rating": "Bewertung", + "search_filters_sort_option_date": "Datum", + "search_filters_sort_option_views": "Aufrufe", + "search_filters_type_label": "Inhaltstyp", + "search_filters_duration_label": "Dauer", + "search_filters_features_label": "Eigenschaften", + "search_filters_sort_label": "sortieren", + "search_filters_date_option_hour": "Letzte Stunde", + "search_filters_date_option_today": "Heute", + "search_filters_date_option_week": "Diese Woche", + "search_filters_date_option_month": "Diesen Monat", + "search_filters_date_option_year": "Dieses Jahr", + "search_filters_type_option_video": "Video", + "search_filters_type_option_channel": "Kanal", + "search_filters_type_option_playlist": "Wiedergabeliste", + "search_filters_type_option_movie": "Film", + "search_filters_type_option_show": "Anzeigen", + "search_filters_features_option_hd": "HD", + "search_filters_features_option_subtitles": "Untertitel / CC", + "search_filters_features_option_c_commons": "Creative Commons", + "search_filters_features_option_three_d": "3D", + "search_filters_features_option_live": "Live", + "search_filters_features_option_four_k": "4K", + "search_filters_features_option_location": "Standort", + "search_filters_features_option_hdr": "HDR", "Current version: ": "Aktuelle Version: ", "next_steps_error_message": "Danach folgendes versuchen: ", "next_steps_error_message_refresh": "Aktualisieren", "next_steps_error_message_go_to_youtube": "Zu YouTube gehen", "footer_donate_page": "Spende", - "long": "Lang (> 20 Minuten)", + "search_filters_duration_option_long": "Lang (> 20 Minuten)", "footer_original_source_code": "Original Quellcode", "footer_modfied_source_code": "Modifizierter Quellcode", "footer_documentation": "Dokumentation", "footer_source_code": "Quellcode", "adminprefs_modified_source_code_url_label": "URL zum Repositorie des modifizierten Quellcodes", - "short": "Kurz (< 4 Minuten)", + "search_filters_duration_option_short": "Kurz (< 4 Minuten)", "preferences_region_label": "Land der Inhalte: ", "preferences_quality_option_dash": "DASH (automatische Qualität)", "preferences_quality_option_hd720": "HD720", @@ -388,13 +386,13 @@ "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 im Video speichern: ", - "360": "360°", + "preferences_save_player_pos_label": "Wiedergabeposition speichern: ", + "search_filters_features_option_three_sixty": "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", + "search_filters_features_option_purchased": "Gekauft", "none": "keine", "videoinfo_started_streaming_x_ago": "Stream begann vor `x`", "videoinfo_watch_on_youTube": "Auf YouTube ansehen", @@ -421,7 +419,7 @@ "generic_count_minutes": "{{count}} Minute", "generic_count_minutes_plural": "{{count}} Minuten", "crash_page_read_the_faq": "Das <a href=\"`x`\">FAQ</a> gelesen haben", - "crash_page_search_issue": "Nach <a href=\"`x`\">bereits gemeldeten Bugs auf Github</a> gesucht haben", + "crash_page_search_issue": "Nach <a href=\"`x`\">bereits gemeldeten Bugs auf GitHub</a> gesucht haben", "crash_page_report_issue": "Wenn all dies nicht geholfen hat, <a href=\"`x`\">öffnen Sie bitte ein neues Problem (issue) auf Github</a> (vorzugsweise auf Englisch) und fügen Sie den folgenden Text in Ihre Nachricht ein (bitte übersetzen Sie diesen Text NICHT):", "generic_views_count": "{{count}} Aufruf", "generic_views_count_plural": "{{count}} Aufrufe", @@ -435,5 +433,32 @@ "comments_points_count_plural": "{{count}} Punkte", "crash_page_you_found_a_bug": "Anscheinend haben Sie einen Fehler in Invidious gefunden!", "generic_count_months": "{{count}} Monat", - "generic_count_months_plural": "{{count}} Monate" + "generic_count_months_plural": "{{count}} Monate", + "Cantonese (Hong Kong)": "Kantonesisch (Hong Kong)", + "Chinese (Hong Kong)": "Chinesisch (Hong Kong)", + "generic_playlists_count": "{{count}} Wiedergabeliste", + "generic_playlists_count_plural": "{{count}} Wiedergabelisten", + "preferences_watch_history_label": "Wiedergabeverlauf aktivieren: ", + "English (United Kingdom)": "Englisch (Vereinigtes Königreich)", + "English (United States)": "Englisch (Vereinigte Staaten)", + "Dutch (auto-generated)": "Niederländisch (automatisch generiert)", + "French (auto-generated)": "Französisch (automatisch generiert)", + "German (auto-generated)": "Deutsch (automatisch generiert)", + "Indonesian (auto-generated)": "Indonesisch (automatisch generiert)", + "Interlingue": "Interlingue", + "Italian (auto-generated)": "Italienisch (automatisch generiert)", + "Japanese (auto-generated)": "Japanisch (automatisch generiert)", + "Spanish (Mexico)": "Spanisch (Mexiko)", + "Spanish (Spain)": "Spanisch (Spanien)", + "Vietnamese (auto-generated)": "Vietnamesisch (automatisch generiert)", + "Russian (auto-generated)": "Russisch (automatisch generiert)", + "Chinese": "Chinesisch", + "Portuguese (Brazil)": "Portugiesisch (Brasilien)", + "Spanish (auto-generated)": "Spanisch (automatisch generiert)", + "Turkish (auto-generated)": "Türkisch (automatisch generiert)", + "Chinese (China)": "Chinesisch (China)", + "Chinese (Taiwan)": "Chinesisch (Taiwan)", + "Korean (auto-generated)": "Koreanisch (automatisch generiert)", + "Portuguese (auto-generated)": "Portugiesisch (automatisch generiert)", + "search_filters_title": "Filtern" } diff --git a/locales/el.json b/locales/el.json index 24e42153..048a520b 100644 --- a/locales/el.json +++ b/locales/el.json @@ -358,7 +358,7 @@ "crash_page_before_reporting": "Πριν αναφέρετε ένα σφάλμα, βεβαιωθείτε ότι έχετε:", "crash_page_refresh": "προσπαθήσει να <a href=\"`x`\">ανανεώσετε τη σελίδα</a>", "crash_page_read_the_faq": "διαβάσει τις <a href=\"`x`\">Συχνές Ερωτήσεις (ΣΕ)</a>", - "crash_page_search_issue": "αναζητήσει για <a href=\"`x`\">υπάρχοντα θέματα στο Github</a>", + "crash_page_search_issue": "αναζητήσει για <a href=\"`x`\">υπάρχοντα θέματα στο GitHub</a>", "generic_views_count": "{{count}} προβολή", "generic_views_count_plural": "{{count}} προβολές", "generic_videos_count": "{{count}} βίντεο", @@ -373,51 +373,50 @@ "preferences_region_label": "Χώρα περιεχομένου: ", "preferences_category_misc": "Διάφορες προτιμήσεις", "Show more": "Εμφάνιση περισσότερων", - "today": "Σήμερα", - "360": "360°", + "search_filters_date_option_today": "Σήμερα", + "search_filters_features_option_three_sixty": "360°", "videoinfo_started_streaming_x_ago": "Ξεκίνησε η ροή `x` πριν από", "videoinfo_watch_on_youTube": "Παρακολουθήστε στο YouTube", "download_subtitles": "Υπότιτλοι - `x` (.vtt)", "user_created_playlists": "`x` δημιουργημένες λίστες αναπαραγωγής", "user_saved_playlists": "`x` αποθηκευμένες λίστες αναπαραγωγής", - "rating": "Αξιολόγηση", - "relevance": "Συνάφεια", - "purchased": "Αγορασμένο", - "date": "Ημερομηνία μεταφόρτωσης", - "content_type": "Τύπος", - "duration": "Διάρκεια", - "week": "Αυτή την εβδομάδα", - "year": "Φέτος", - "channel": "Κανάλι", - "playlist": "Λίστα αναπαραγωγής", - "long": "Μεγάλο (> 20 λεπτά)", - "hd": "HD", - "location": "Τοποθεσία", - "3d": "3D", + "search_filters_sort_option_rating": "Αξιολόγηση", + "search_filters_sort_option_relevance": "Συνάφεια", + "search_filters_features_option_purchased": "Αγορασμένο", + "search_filters_sort_option_date": "Ημερομηνία μεταφόρτωσης", + "search_filters_type_label": "Τύπος", + "search_filters_duration_label": "Διάρκεια", + "search_filters_date_option_week": "Αυτή την εβδομάδα", + "search_filters_date_option_year": "Φέτος", + "search_filters_type_option_channel": "Κανάλι", + "search_filters_type_option_playlist": "Λίστα αναπαραγωγής", + "search_filters_duration_option_long": "Μεγάλο (> 20 λεπτά)", + "search_filters_features_option_hd": "HD", + "search_filters_features_option_location": "Τοποθεσία", + "search_filters_features_option_three_d": "3D", "next_steps_error_message": "Μετά από αυτό θα πρέπει να προσπαθήσετε να: ", "next_steps_error_message_go_to_youtube": "Μεταβείτε στο YouTube", "footer_donate_page": "Δωρεά", "footer_original_source_code": "Πρωτότυπος πηγαίος κώδικας", "preferences_show_nick_label": "Εμφάνιση ψευδώνυμου στην κορυφή: ", - "hour": "Τελευταία ώρα", + "search_filters_date_option_hour": "Τελευταία ώρα", "adminprefs_modified_source_code_url_label": "URL σε αποθετήριο τροποποιημένου πηγαίου κώδικα", - "subtitles": "Υπότιτλοι/CC", - "month": "Αυτόν τον μήνα", - "Released under the AGPLv3 on Github.": "Κυκλοφορεί υπό την AGPLv3 στο Github.", - "sort": "Ταξινόμηση κατά", - "filter": "Φίλτρο", - "movie": "Ταινία", + "search_filters_features_option_subtitles": "Υπότιτλοι/CC", + "search_filters_date_option_month": "Αυτόν τον μήνα", + "Released under the AGPLv3 on Github.": "Κυκλοφορεί υπό την AGPLv3 στο GitHub.", + "search_filters_sort_label": "Ταξινόμηση κατά", + "search_filters_type_option_movie": "Ταινία", "footer_modfied_source_code": "Τροποποιημένος πηγαίος κώδικας", - "features": "Χαρακτηριστικά", - "4k": "4K", + "search_filters_features_label": "Χαρακτηριστικά", + "search_filters_features_option_four_k": "4K", "footer_documentation": "Τεκμηρίωση", - "short": "Σύντομο (< 4 λεπτά)", + "search_filters_duration_option_short": "Σύντομο (< 4 λεπτά)", "next_steps_error_message_refresh": "Ανανέωση", - "video": "Βίντεο", - "live": "Ζωντανά", - "creative_commons": "Creative Commons", + "search_filters_type_option_video": "Βίντεο", + "search_filters_features_option_live": "Ζωντανά", + "search_filters_features_option_c_commons": "Creative Commons", "Search": "Αναζήτηση", - "hdr": "HDR", + "search_filters_features_option_hdr": "HDR", "preferences_extend_desc_label": "Αυτόματη επέκταση της περιγραφής του βίντεο: ", "preferences_vr_mode_label": "Διαδραστικά βίντεο 360 μοιρών (απαιτεί WebGL): ", "Show less": "Εμφάνιση λιγότερων", @@ -448,6 +447,7 @@ "none": "κανένα", "videoinfo_youTube_embed_link": "Ενσωμάτωση", "videoinfo_invidious_embed_link": "Σύνδεσμος Ενσωμάτωσης", - "show": "Μπάρα προόδου διαβάσματος", - "preferences_watch_history_label": "Ενεργοποίηση ιστορικού παρακολούθησης: " + "search_filters_type_option_show": "Μπάρα προόδου διαβάσματος", + "preferences_watch_history_label": "Ενεργοποίηση ιστορικού παρακολούθησης: ", + "search_filters_title": "Φίλτρο" } diff --git a/locales/en-US.json b/locales/en-US.json index a78d8062..c57670fc 100644 --- a/locales/en-US.json +++ b/locales/en-US.json @@ -155,7 +155,7 @@ "subscriptions_unseen_notifs_count_plural": "{{count}} unseen notifications", "search": "search", "Log out": "Log out", - "Released under the AGPLv3 on Github.": "Released under the AGPLv3 on Github.", + "Released under the AGPLv3 on Github.": "Released under the AGPLv3 on GitHub.", "Source available here.": "Source available here.", "View JavaScript license information.": "View JavaScript license information.", "View privacy policy.": "View privacy policy.", @@ -175,7 +175,9 @@ "Show less": "Show less", "Watch on YouTube": "Watch on YouTube", "Switch Invidious Instance": "Switch Invidious Instance", - "Broken? Try another Invidious Instance": "Broken? Try another Invidious Instance", + "search_message_no_results": "No results found.", + "search_message_change_filters_or_query": "Try widening your search query and/or changing the filters.", + "search_message_use_another_instance": " You can also <a href=\"`x`\">search on another instance</a>.", "Hide annotations": "Hide annotations", "Show annotations": "Show annotations", "Genre: ": "Genre: ", @@ -404,37 +406,44 @@ "Videos": "Videos", "Playlists": "Playlists", "Community": "Community", - "relevance": "Relevance", - "rating": "Rating", - "date": "Upload date", - "views": "View count", - "content_type": "Type", - "duration": "Duration", - "features": "Features", - "sort": "Sort By", - "hour": "Last Hour", - "today": "Today", - "week": "This week", - "month": "This month", - "year": "This year", - "video": "Video", - "channel": "Channel", - "playlist": "Playlist", - "movie": "Movie", - "show": "Show", - "short": "Short (< 4 minutes)", - "long": "Long (> 20 minutes)", - "hd": "HD", - "subtitles": "Subtitles/CC", - "creative_commons": "Creative Commons", - "3d": "3D", - "live": "Live", - "4k": "4K", - "location": "Location", - "hdr": "HDR", - "purchased": "Purchased", - "360": "360°", - "filter": "Filter", + "search_filters_title": "Filters", + "search_filters_date_label": "Upload date", + "search_filters_date_option_none": "Any date", + "search_filters_date_option_hour": "Last Hour", + "search_filters_date_option_today": "Today", + "search_filters_date_option_week": "This week", + "search_filters_date_option_month": "This month", + "search_filters_date_option_year": "This year", + "search_filters_type_label": "Type", + "search_filters_type_option_all": "Any type", + "search_filters_type_option_video": "Video", + "search_filters_type_option_channel": "Channel", + "search_filters_type_option_playlist": "Playlist", + "search_filters_type_option_movie": "Movie", + "search_filters_type_option_show": "Show", + "search_filters_duration_label": "Duration", + "search_filters_duration_option_none": "Any duration", + "search_filters_duration_option_short": "Short (< 4 minutes)", + "search_filters_duration_option_medium": "Medium (4 - 20 minutes)", + "search_filters_duration_option_long": "Long (> 20 minutes)", + "search_filters_features_label": "Features", + "search_filters_features_option_live": "Live", + "search_filters_features_option_four_k": "4K", + "search_filters_features_option_hd": "HD", + "search_filters_features_option_subtitles": "Subtitles/CC", + "search_filters_features_option_c_commons": "Creative Commons", + "search_filters_features_option_three_sixty": "360°", + "search_filters_features_option_vr180": "VR180", + "search_filters_features_option_three_d": "3D", + "search_filters_features_option_hdr": "HDR", + "search_filters_features_option_location": "Location", + "search_filters_features_option_purchased": "Purchased", + "search_filters_sort_label": "Sort By", + "search_filters_sort_option_relevance": "Relevance", + "search_filters_sort_option_rating": "Rating", + "search_filters_sort_option_date": "Upload Date", + "search_filters_sort_option_views": "View count", + "search_filters_apply_button": "Apply selected filters", "Current version: ": "Current version: ", "next_steps_error_message": "After which you should try to: ", "next_steps_error_message_refresh": "Refresh", @@ -460,6 +469,6 @@ "crash_page_refresh": "tried to <a href=\"`x`\">refresh the page</a>", "crash_page_switch_instance": "tried to <a href=\"`x`\">use another instance</a>", "crash_page_read_the_faq": "read the <a href=\"`x`\">Frequently Asked Questions (FAQ)</a>", - "crash_page_search_issue": "searched for <a href=\"`x`\">existing issues on Github</a>", + "crash_page_search_issue": "searched for <a href=\"`x`\">existing issues on GitHub</a>", "crash_page_report_issue": "If none of the above helped, please <a href=\"`x`\">open a new issue on GitHub</a> (preferably in English) and include the following text in your message (do NOT translate that text):" } diff --git a/locales/eo.json b/locales/eo.json index e7a8453e..40ab5f39 100644 --- a/locales/eo.json +++ b/locales/eo.json @@ -121,7 +121,7 @@ "Subscriptions": "Abonoj", "search": "serĉi", "Log out": "Elsaluti", - "Released under the AGPLv3 on Github.": "Eldonita sub la AGPLv3 en Github.", + "Released under the AGPLv3 on Github.": "Eldonita sub la AGPLv3 en GitHub.", "Source available here.": "Fonto havebla ĉi tie.", "View JavaScript license information.": "Vidi Ĝavoskriptan licencan informon.", "View privacy policy.": "Vidi regularon pri privateco.", @@ -141,7 +141,6 @@ "Show less": "Montri malpli", "Watch on YouTube": "Vidi filmeton en JuTubo", "Switch Invidious Instance": "Ŝanĝi instalaĵon de Indivious", - "Broken? Try another Invidious Instance": "Ĉu misfunkcio? Provu alian instalaĵon de Indivious", "Hide annotations": "Kaŝi prinotojn", "Show annotations": "Montri prinotojn", "Genre: ": "Ĝenro: ", @@ -329,39 +328,38 @@ "Videos": "Filmetoj", "Playlists": "Ludlistoj", "Community": "Komunumo", - "relevance": "rilateco", - "rating": "takso", - "date": "dato", - "views": "vidoj", - "content_type": "enhavtipo", - "duration": "daŭro", - "features": "trajtoj", - "sort": "ordigi", - "hour": "horo", - "today": "hodiaŭ", - "week": "semajno", - "month": "monato", - "year": "jaro", - "video": "filmeto", - "channel": "kanalo", - "playlist": "ludlisto", - "movie": "filmo", - "show": "spektaĵo", - "hd": "altdistingiva", - "subtitles": "subtekstoj", - "creative_commons": "Krea Komunaĵo", - "3d": "3D", - "live": "nuna", - "4k": "4k", - "location": "loko", - "hdr": "granddinamikgama", - "filter": "filtri", + "search_filters_sort_option_relevance": "rilateco", + "search_filters_sort_option_rating": "takso", + "search_filters_sort_option_date": "dato", + "search_filters_sort_option_views": "vidoj", + "search_filters_type_label": "enhavtipo", + "search_filters_duration_label": "daŭro", + "search_filters_features_label": "trajtoj", + "search_filters_sort_label": "ordigi", + "search_filters_date_option_hour": "horo", + "search_filters_date_option_today": "hodiaŭ", + "search_filters_date_option_week": "semajno", + "search_filters_date_option_month": "monato", + "search_filters_date_option_year": "jaro", + "search_filters_type_option_video": "filmeto", + "search_filters_type_option_channel": "kanalo", + "search_filters_type_option_playlist": "ludlisto", + "search_filters_type_option_movie": "filmo", + "search_filters_type_option_show": "spektaĵo", + "search_filters_features_option_hd": "altdistingiva", + "search_filters_features_option_subtitles": "subtekstoj", + "search_filters_features_option_c_commons": "Krea Komunaĵo", + "search_filters_features_option_three_d": "3D", + "search_filters_features_option_live": "nuna", + "search_filters_features_option_four_k": "4k", + "search_filters_features_option_location": "loko", + "search_filters_features_option_hdr": "granddinamikgama", "Current version: ": "Nuna versio: ", "next_steps_error_message": "Poste, vi provu: ", "next_steps_error_message_refresh": "Reŝargi", "next_steps_error_message_go_to_youtube": "Iri al JuTubo", - "long": "Longa (> 20 minutos)", - "short": "Mallonga (< 4 minutos)", + "search_filters_duration_option_long": "Longa (> 20 minutos)", + "search_filters_duration_option_short": "Mallonga (< 4 minutos)", "footer_documentation": "Dokumentaro", "footer_source_code": "Fontkodo", "adminprefs_modified_source_code_url_label": "URL al modifita deponejo de fontkodo", @@ -369,5 +367,6 @@ "footer_original_source_code": "Originala fontkodo", "footer_donate_page": "Donaci", "preferences_region_label": "Lando de la enhavo: ", - "preferences_quality_dash_label": "Preferata DASH-a videkvalito: " + "preferences_quality_dash_label": "Preferata DASH-a videkvalito: ", + "search_filters_title": "Filtri" } diff --git a/locales/es.json b/locales/es.json index 689cb310..0958a736 100644 --- a/locales/es.json +++ b/locales/es.json @@ -121,7 +121,7 @@ "Subscriptions": "Suscripciones", "search": "buscar", "Log out": "Cerrar la sesión", - "Released under the AGPLv3 on Github.": "Publicado bajo la AGPLv3 en Github.", + "Released under the AGPLv3 on Github.": "Publicado bajo la AGPLv3 en GitHub.", "Source available here.": "Código fuente disponible aquí.", "View JavaScript license information.": "Ver información de licencia de JavaScript.", "View privacy policy.": "Ver la política de privacidad.", @@ -141,7 +141,6 @@ "Show less": "Mostrar menos", "Watch on YouTube": "Ver el vídeo en YouTube", "Switch Invidious Instance": "Cambiar Instancia de Invidious", - "Broken? Try another Invidious Instance": "¿Algún error? Prueba otra instancia de Invidious", "Hide annotations": "Ocultar anotaciones", "Show annotations": "Mostrar anotaciones", "Genre: ": "Género: ", @@ -329,39 +328,38 @@ "Videos": "Vídeos", "Playlists": "Listas de reproducción", "Community": "Comunidad", - "relevance": "relevancia", - "rating": "valoración", - "date": "fecha", - "views": "visualizaciones", - "content_type": "content_type", - "duration": "duración", - "features": "funcionalidades", - "sort": "ordenar", - "hour": "hora", - "today": "hoy", - "week": "semana", - "month": "mes", - "year": "año", - "video": "vídeo", - "channel": "canal", - "playlist": "lista de reproducción", - "movie": "película", - "show": "programa", - "hd": "hd", - "subtitles": "subtítulos", - "creative_commons": "creative_commons", - "3d": "3d", - "live": "directo", - "4k": "4k", - "location": "ubicación", - "hdr": "hdr", - "filter": "filtro", + "search_filters_sort_option_relevance": "relevancia", + "search_filters_sort_option_rating": "valoración", + "search_filters_sort_option_date": "fecha", + "search_filters_sort_option_views": "visualizaciones", + "search_filters_type_label": "content_type", + "search_filters_duration_label": "duración", + "search_filters_features_label": "funcionalidades", + "search_filters_sort_label": "ordenar", + "search_filters_date_option_hour": "hora", + "search_filters_date_option_today": "hoy", + "search_filters_date_option_week": "semana", + "search_filters_date_option_month": "mes", + "search_filters_date_option_year": "año", + "search_filters_type_option_video": "vídeo", + "search_filters_type_option_channel": "canal", + "search_filters_type_option_playlist": "lista de reproducción", + "search_filters_type_option_movie": "película", + "search_filters_type_option_show": "programa", + "search_filters_features_option_hd": "hd", + "search_filters_features_option_subtitles": "subtítulos", + "search_filters_features_option_c_commons": "creative_commons", + "search_filters_features_option_three_d": "3d", + "search_filters_features_option_live": "directo", + "search_filters_features_option_four_k": "4k", + "search_filters_features_option_location": "ubicación", + "search_filters_features_option_hdr": "hdr", "Current version: ": "Versión actual: ", "next_steps_error_message": "Después de lo cual deberías intentar: ", "next_steps_error_message_refresh": "Recargar la página", "next_steps_error_message_go_to_youtube": "Ir a YouTube", - "short": "Corto (< 4 minutos)", - "long": "Largo (> 20 minutos)", + "search_filters_duration_option_short": "Corto (< 4 minutos)", + "search_filters_duration_option_long": "Largo (> 20 minutos)", "footer_documentation": "Documentación", "footer_original_source_code": "Código fuente original", "adminprefs_modified_source_code_url_label": "URL al repositorio de código fuente modificado", @@ -395,8 +393,8 @@ "preferences_quality_dash_option_worst": "La peor", "videoinfo_invidious_embed_link": "Enlace para Insertar", "preferences_quality_dash_option_1080p": "1080p", - "purchased": "Comprado", - "360": "360°", + "search_filters_features_option_purchased": "Comprado", + "search_filters_features_option_three_sixty": "360°", "videoinfo_watch_on_youTube": "Ver en YouTube", "preferences_save_player_pos_label": "Guardar posición de reproducción: ", "generic_views_count": "{{count}} visualización", @@ -432,7 +430,7 @@ "crash_page_before_reporting": "Antes de notificar un error asegúrate de que has:", "crash_page_switch_instance": "probado a <a href=\"`x`\">usar otra instancia</a>", "crash_page_read_the_faq": "leído las <a href=\"`x`\">Preguntas Frecuentes</a>", - "crash_page_search_issue": "buscado <a href=\"`x`\">problemas existentes en Github</a>", + "crash_page_search_issue": "buscado <a href=\"`x`\">problemas existentes en GitHub</a>", "crash_page_you_found_a_bug": "¡Parece que has encontrado un error en Invidious!", "crash_page_refresh": "probado a <a href=\"`x`\">recargar la página</a>", "crash_page_report_issue": "Si nada de lo anterior ha sido de ayuda, por favor, <a href=\"`x`\">abre una nueva incidencia en GitHub</a> (preferiblemente en inglés) e incluye el siguiente texto en tu mensaje (NO traduzcas este texto):", @@ -459,5 +457,18 @@ "Korean (auto-generated)": "Coreano (generados automáticamente)", "Spanish (Mexico)": "Español (Méjico)", "Spanish (auto-generated)": "Español (generados automáticamente)", - "preferences_watch_history_label": "Habilitar historial de reproducciones: " + "preferences_watch_history_label": "Habilitar historial de reproducciones: ", + "search_message_no_results": "No se han encontrado resultados.", + "search_message_change_filters_or_query": "Pruebe ampliar la consulta de búsqueda y/o a cambiar los filtros.", + "search_filters_title": "Filtros", + "search_filters_date_label": "Fecha de subida", + "search_filters_date_option_none": "Cualquier fecha", + "search_filters_type_option_all": "Cualquier tipo", + "search_filters_duration_option_none": "Cualquier duración", + "search_filters_features_option_vr180": "VR180", + "search_filters_apply_button": "Aplicar filtros seleccionados", + "tokens_count": "{{count}} token", + "tokens_count_plural": "{{count}} tokens", + "search_message_use_another_instance": " También puede <a href=\"`x`\">buscar en otra instancia</a>.", + "search_filters_duration_option_medium": "Medio (4 - 20 minutes)" } diff --git a/locales/eu.json b/locales/eu.json index a5c7c562..9e093a52 100644 --- a/locales/eu.json +++ b/locales/eu.json @@ -20,15 +20,15 @@ "No": "Ez", "Import and Export Data": "Datuak inportatu eta esportatu", "Import": "Inportatu", - "Import Invidious data": "Inportatu Invidiouseko datuak", - "Import YouTube subscriptions": "Inportatu YouTubeko harpidetzak", + "Import Invidious data": "Inportatu Invidiouseko JSON datuak", + "Import YouTube subscriptions": "Inportatu YouTubeko/OPML harpidetzak", "Import FreeTube subscriptions (.db)": "Inportatu FreeTubeko harpidetzak (.db)", "Import NewPipe subscriptions (.json)": "Inportatu NewPipeko harpidetzak (.json)", "Import NewPipe data (.zip)": "Inportatu NewPipeko datuak (.zip)", "Export": "Esportatu", "Export subscriptions as OPML": "Esportatu harpidetzak OPML bezala", "Export subscriptions as OPML (for NewPipe & FreeTube)": "Esportatu harpidetzak OPML bezala (NewPipe eta FreeTuberako)", - "Export data as JSON": "Esportatu datuak JSON bezala", + "Export data as JSON": "Esportatu Invidious datuak JSON gisa", "Delete account?": "Kontua ezabatu?", "History": "Historia", "An alternative front-end to YouTube": "YouTuberako interfaze alternatibo bat", @@ -53,7 +53,7 @@ "preferences_volume_label": "Erreproduzigailuaren bolumena: ", "preferences_comments_label": "Lehenetsitako iruzkinak: ", "youtube": "YouTube", - "reddit": "reddit", + "reddit": "Reddit", "preferences_captions_label": "Lehenetsitako azpitituluak: ", "preferences_related_videos_label": "Erakutsi erlazionatutako bideoak: ", "preferences_annotations_label": "Erakutsi oharrak modu lehenetsian: ", @@ -62,5 +62,216 @@ "Dark mode: ": "Gai iluna: ", "preferences_dark_mode_label": "Gaia: ", "dark": "iluna", - "light": "argia" + "light": "argia", + "generic_subscriptions_count": "{{count}} harpidetza", + "generic_subscriptions_count_plural": "{{count}} harpidetzak", + "tokens_count": "{{count}} tokena", + "tokens_count_plural": "{{count}} tokenak", + "comments_points_count": "{{count}} puntua", + "comments_points_count_plural": "{{count}} puntuak", + "View more comments on Reddit": "Iruzkin gehiago Redditen", + "Fallback captions: ": "Ordezko azpitituluak: ", + "generic_subscribers_count": "{{count}} harpidedun", + "generic_subscribers_count_plural": "{{count}} harpidedunak", + "preferences_quality_option_dash": "DASH (kalitate egokitua)", + "preferences_listen_label": "Lehenetsiz jo: ", + "preferences_speed_label": "Abiadura lehenetsia: ", + "preferences_quality_dash_option_2160p": "2160p", + "preferences_quality_dash_option_144p": "144p", + "preferences_quality_dash_option_auto": "Auto", + "preferences_quality_dash_option_worst": "Txarrena", + "preferences_quality_dash_option_best": "Hoberena", + "preferences_quality_dash_option_4320p": "4320p", + "preferences_quality_dash_option_480p": "480p", + "preferences_quality_dash_option_240p": "240p", + "preferences_extend_desc_label": "Bideoaren azalpena automatikoki zabaldu: ", + "preferences_annotations_subscribed_label": "Harpidetutako kanalen oharrak erakutsi lehenetsiz? ", + "Redirect homepage to feed: ": "Hasierako orrira bidali jarraitzeko: ", + "channel name - reverse": "kanalaren izena - alderantziz", + "preferences_notifications_only_label": "Jakinarazpenak soilik erakutsi (baldin badago): ", + "Top enabled: ": "Goikoa gaitu: ", + "Import/export data": "Inportatu/exportatu data", + "Create playlist": "Zerrenda sortu", + "Hi! Looks like you have JavaScript turned off. Click here to view comments, keep in mind they may take a bit longer to load.": "Aditu! JavaScript itzalita dakazula ematen du. Hemen sakatu iruzkinak ikusteko. Denbora luza leikeela kontuan hartu.", + "Unable to log in, make sure two-factor authentication (Authenticator or SMS) is turned on.": "Ezinezkoa izena eman. Ziurtatu berresteko bi faktoreak (Authenticator edo SMS) piztuta daudela.", + "generic_views_count": "{{count}}ikusia", + "generic_views_count_plural": "{{count}}ikusiak", + "generic_playlists_count": "{{count}}zerrenda", + "generic_playlists_count_plural": "{{count}}zerrendak", + "Could not fetch comments": "Iruzkinei ezin heldu", + "Erroneous token": "Token okerra", + "Albanian": "Albaniarra", + "Azerbaijani": "Azerbaitarra", + "No such user": "Ez dago erabiltzailerik", + "Bulgarian": "Bulgariarra", + "Filipino": "Filipinera", + "French": "Frantsesa", + "French (auto-generated)": "Frantsesa (auto-sortua)", + "Show more": "Erakutsi gehiago", + "Show less": "Erakutsi gutxiago", + "Delete playlist": "Zerrenda ezabatu", + "Delete account": "Kontua ezabatu", + "User ID is a required field": "Erabiltzailearen IDa beharrezkoa da", + "English (United Kingdom)": "Ingelesa (Britania Handia", + "preferences_vr_mode_label": "360 graduko bideo interaktiboak (WebGL beharko): ", + "English (United States)": "Estatu batuarra (AEB)", + "English (auto-generated)": "Ingelesa (autosortua)", + "Arabic": "Arabiarra", + "Armenian": "Armeniarra", + "Bangla": "Banglera", + "Belarusian": "Bielorrusiara", + "Burmese": "Burmesera", + "Chinese (Simplified)": "Txinera (sinplifikatua)", + "preferences_watch_history_label": "Baimendu historia ikusi ", + "generic_videos_count": "{{count}}bideo", + "generic_videos_count_plural": "{{count}}bideoak", + "View privacy policy.": "Pribatutasun politika ikusi.", + "Cantonese (Hong Kong)": "Kantoniera (Hong Kong)", + "subscriptions_unseen_notifs_count": "{{count}} ezikusitako oharra", + "subscriptions_unseen_notifs_count_plural": "{{count}} ezikusitako oharrak", + "Trending": "Joera", + "Playlist privacy": "Zerrendaren privatutasuna", + "Switch Invidious Instance": "Invidious adibidea aldatu", + "Genre: ": "Genero: ", + "License: ": "Lizentzia: ", + "Family friendly? ": "Adeikorra familiarekin? ", + "Wilson score: ": "Wilsonen puntuazioa: ", + "Quota exceeded, try again in a few hours": "Kuota gaindituta, ordu batzuren bueltan berriro saiatu", + "comments_view_x_replies": "{{count}} erantzuna ikusi", + "comments_view_x_replies_plural": "{{count}} erantzunak ikusi", + "Catalan": "Katalaniera", + "Chinese": "Txinera", + "Chinese (China)": "Txinatarra", + "Chinese (Hong Kong)": "Hongkondarra", + "Chinese (Taiwan)": "Taiwandarra", + "Corsican": "Korsikera", + "Dutch (auto-generated)": "Alemaniera (auto-sortua)", + "Estonian": "Estoniera", + "Finnish": "Finlandiera", + "Galician": "Galizera", + "German (auto-generated)": "Alemaiera (auto-sortua)", + "Greek": "Greziera", + "crash_page_report_issue": "Aurreko ezerk ez badizu lagundu, arren <a href=\"`x`\"> GitHuben gai berri bat zabaldu </a> (ingelesez ahal bada) eta zure mezuan hurrengo testua sartu (testuari EZ itzulpena egin):", + "crash_page_search_issue": "GitHuben dauden gaiak <a href=\"`x`\"> buruz</a>", + "preferences_quality_option_medium": "Erdixka", + "preferences_quality_option_small": "Txikia", + "preferences_quality_dash_label": "DASH bideo kalitate lehenetsia: ", + "preferences_quality_dash_option_1440p": "1440p", + "preferences_quality_dash_option_1080p": "1080p", + "preferences_quality_dash_option_720p": "720p", + "preferences_quality_option_hd720": "HD720", + "preferences_quality_dash_option_360p": "360p", + "invidious": "Invidious", + "Source available here.": "Iturburua hemen eskura.", + "View JavaScript license information.": "JavaScriptaren lizentzi adierazpena ikusi.", + "Blacklisted regions: ": "zerrenda beltzaren zonaldeak: ", + "Premieres `x`": "'x' estrenaldiak", + "Wrong answer": "Erantzun ez zuzena", + "Password is a required field": "Pasahitza beharrezkoa da", + "Wrong username or password": "Pasahitza edo ezizena gaizki", + "Password cannot be longer than 55 characters": "Pasahitza 55 karaktere baino luzeagoa ezin da izan", + "This channel does not exist.": "Kanal hau ez dago.", + "`x` ago": "duela 'x'", + "Czech": "Txekiera", + "preferences_region_label": "Herrialdeko edukiera: ", + "preferences_sort_label": "Bideoak ordenatu: ", + "published": "argitaratuta", + "Only show latest video from channel: ": "Kanalaren azken bideoa soilik erakutsi ", + "preferences_category_admin": "Administratzailearen lehentasunak", + "Registration enabled: ": "Harpidetza gaituta: ", + "Save preferences": "Baloreak gorde", + "Token manager": "Token kudeatzailea", + "unsubscribe": "Baja eman", + "search": "Bilatu", + "Log out": "Irten", + "English": "Ingelesa", + "Afrikaans": "Afrikarra", + "Amharic": "Amharerra", + "Basque": "Euskera", + "Bosnian": "Bosniarra", + "Cebuano": "Zebuera", + "Chinese (Traditional)": "Txinera (Tradizionala)", + "Croatian": "Croaziera", + "Danish": "Daniera", + "Dutch": "Alemaniera", + "Esperanto": "Esperanto", + "Erroneous challenge": "Erronka okerra", + "View all playlists": "Zerrenda guztiak ikusi", + "Show annotations": "Oharrak erakutsi", + "Empty playlist": "Zerrenda hutsik", + "Please log in": "Sartu, mesedez", + "CAPTCHA is a required field": "CAPTCHA beharrezko eremua da", + "preferences_category_data": "Dataren lehentasunak", + "preferences_default_home_label": "Homepage lehenetsia: ", + "preferences_automatic_instance_redirect_label": "berbideratze adibide automatikoa (atzera egin berbideratzeko: invidious.io) ", + "Please sign in using 'Log in with Google'": "'Log in Googlerekin' erabili", + "`x` uploaded a video": "' x'(e)k bideo bat igo du", + "published - reverse": "argitaratuta - alderantziz", + "Could not get channel info.": "Kanalaren adierazpena ezin lortu.", + "alphabetically - reverse": "alfabetikoki - alderantziz", + "Public": "Orokorra", + "Unlisted": "Ez zerrendatua", + "Subscription manager": "Harpidetzen kudeatzailea", + "Updated `x` ago": "Duela 'x' eguneratua", + "Hide replies": "Erantzunak izkutatu", + "preferences_thin_mode_label": "Urri eran: ", + "Show replies": "Erantzunak erakutsi", + "Watch on YouTube": "YouTuben ikusi", + "Premieres in `x`": "'x'eko estrenaldiak", + "Delete playlist `x`?": "'x' zerrenda ezabatu nahi?", + "Token is expired, please try again": "Token kadukatua, saiatu berriro", + "Invalid TFA code": "TFA kodea ez da zuzena", + "CAPTCHA enabled: ": "CAPTCHA gaitu: ", + "Released under the AGPLv3 on Github.": "GitHubeko AGPLv3pean argitaratuta.", + "channel:`x`": "Kanal: 'x'", + "Georgian": "Georgiera", + "Incorrect password": "Pasahitza gaizki", + "Playlist does not exist.": "Zerrenda ez da existitzen.", + "preferences_category_misc": "Askotariko lehentasunak", + "View `x` comments": { + "([^.,0-9]|^)1([^.,0-9]|$)": "'x' iruzkina ikusi", + "": "'x' iruzkinak ikusi" + }, + "Report statistics: ": "Estatistikak adierazi: ", + "preferences_max_results_label": "Jotzeko bideo zerrendaren luzera: ", + "Subscriptions": "Harpidetzak", + "Load more": "Gehiago atera", + "Change password": "Pasahitza aldatu", + "preferences_show_nick_label": "Erakutsi ezizena goian: ", + "View Reddit comments": "Redditeko iruzkinak ikusi", + "preferences_category_subscription": "Harpidetzaren lehentasunak", + "Hidden field \"challenge\" is a required field": "\"challenge\" eremu ezkutua beharrezkoa da", + "German": "Alemaniarra", + "Login failed. This may be because two-factor authentication is not turned on for your account.": "Ezin izena eman. Izan leike zure konturako berresteko bi faktoreak piztuta ez daudela.", + "View YouTube comments": "YouTubeko iruzkinak ikusi", + "Google verification code": "Googleren berresteko kodea", + "`x` is live": "'x' bizirik darrai", + "Password cannot be empty": "Pasahitza ezin da hutsik utzi", + "preferences_video_loop_label": "Beti begiztatu: ", + "Only show latest unwatched video from channel: ": "kanalaren azken bideo ezikusia erakutsi soilik ", + "Enable web notifications": "Webaren jakinarazpenak baimendu", + "revoke": "ukatu", + "preferences_continue_label": "Hurrengo lehenetsia jo: ", + "Whitelisted regions: ": "Zuri zerrendaren zonaldeak: ", + "Erroneous CAPTCHA": "CAPTCHA gaizki", + "Deleted or invalid channel": "Ezgai edota ezabatutako kanala", + "Could not create mix.": "Nahastea ezin sortu.", + "Not a playlist.": "Ez da zerrenda.", + "Hidden field \"token\" is a required field": "\"token\" eremu ezkutua beharrezkoa da", + "Import/export": "Inportatu/esportatu", + "alphabetically": "alfabetikoki", + "preferences_unseen_only_label": "Ezikusiak besterik ez erakutsi: ", + "Clear watch history": "Historia ezabatu", + "Manage subscriptions": "Harpidetzak kudeatu", + "Manage tokens": "Fitxak kudeatu", + "Watch history": "Historia ikusi", + "Login enabled: ": "Login gaitu: ", + "Hide annotations": "Oharrak izkutatu", + "Title": "Titulua", + "channel name": "Kanalaren izena", + "Authorize token for `x`?": "Baimendu tokena 'x'tzako?", + "Private": "Pribatua", + "Editing playlist `x`": "'x' zerrenda editatu", + "Could not pull trending pages.": "Ezin ekarri orri arrakastatsuak.", + "crash_page_read_the_faq": "Bide <a href=\"`x`\"> (FAQ) ohiko galderak</a>" } diff --git a/locales/fa.json b/locales/fa.json index 48b5a17d..5ea976f5 100644 --- a/locales/fa.json +++ b/locales/fa.json @@ -148,7 +148,6 @@ "Show less": "نمایش کمتر", "Watch on YouTube": "تماشا در یوتیوب", "Switch Invidious Instance": "تعویض نمونه اینویدیوس", - "Broken? Try another Invidious Instance": "کار نمیکند؟ نمونه دیگری از اینویدیوس را امتحان کنید", "Hide annotations": "مخفی کردن حاشیه نویسی ها", "Show annotations": "نمایش حاشیه نویسی ها", "Genre: ": "ژانر: ", @@ -345,33 +344,32 @@ "Videos": "ویدیو ها", "Playlists": "سیاهههای پخش", "Community": "اجتماع", - "relevance": "مرتبط بودن", - "rating": "امتیاز", - "date": "تاریخ بارگذاری", - "views": "تعداد بازدید", - "content_type": "نوع", - "duration": "مدت", - "features": "ویژگیها", - "sort": "به ترتیب", - "hour": "یک ساعت گذشته", - "today": "امروز", - "week": "این هفته", - "month": "این ماه", - "year": "امسال", - "video": "ویدئو", - "channel": "کانال", - "playlist": "سیاههٔ پخش", - "movie": "فیلم", - "show": "نمایش", - "hd": "HD", - "subtitles": "زیرنویس", - "creative_commons": "کریتیو کامونز", - "3d": "سهبعدی", - "live": "زنده", - "4k": "4K", - "location": "مکان", - "hdr": "HDR", - "filter": "پالایه", + "search_filters_sort_option_relevance": "مرتبط بودن", + "search_filters_sort_option_rating": "امتیاز", + "search_filters_sort_option_date": "تاریخ بارگذاری", + "search_filters_sort_option_views": "تعداد بازدید", + "search_filters_type_label": "نوع", + "search_filters_duration_label": "مدت", + "search_filters_features_label": "ویژگیها", + "search_filters_sort_label": "به ترتیب", + "search_filters_date_option_hour": "یک ساعت گذشته", + "search_filters_date_option_today": "امروز", + "search_filters_date_option_week": "این هفته", + "search_filters_date_option_month": "این ماه", + "search_filters_date_option_year": "امسال", + "search_filters_type_option_video": "ویدئو", + "search_filters_type_option_channel": "کانال", + "search_filters_type_option_playlist": "سیاههٔ پخش", + "search_filters_type_option_movie": "فیلم", + "search_filters_type_option_show": "نمایش", + "search_filters_features_option_hd": "HD", + "search_filters_features_option_subtitles": "زیرنویس", + "search_filters_features_option_c_commons": "کریتیو کامونز", + "search_filters_features_option_three_d": "سهبعدی", + "search_filters_features_option_live": "زنده", + "search_filters_features_option_four_k": "4K", + "search_filters_features_option_location": "مکان", + "search_filters_features_option_hdr": "HDR", "Current version: ": "نسخه فعلی: ", "next_steps_error_message": "اکنون بایستی یکی از این موارد را امتحان کنید: ", "next_steps_error_message_refresh": "تازهسازی", @@ -393,7 +391,7 @@ "preferences_quality_dash_option_240p": "240p", "preferences_quality_dash_option_144p": "144p", "invidious": "اینویدیوس", - "360": "360°", + "search_filters_features_option_three_sixty": "360°", "footer_donate_page": "کمک مالی", "footer_source_code": "کد منبع", "footer_modfied_source_code": "کد منبع ویرایش شده", @@ -405,12 +403,13 @@ "download_subtitles": "زیرنویسها - `x` (.vtt)", "Video unavailable": "ویدئو دردسترس نیست", "preferences_save_player_pos_label": "ذخیره زمان کنونی ویدئو: ", - "purchased": "خریداری شده", + "search_filters_features_option_purchased": "خریداری شده", "preferences_quality_dash_label": "کیفیت ترجیحی ویدئو DASH: ", "preferences_region_label": "کشور محتوا: ", "footer_documentation": "مستندات", "footer_original_source_code": "کد منبع اصلی", - "long": "بلند (> 20 دقیقه)", + "search_filters_duration_option_long": "بلند (> 20 دقیقه)", "adminprefs_modified_source_code_url_label": "URL مخزن کد منبع ویریش شده", - "short": "کوتاه (< 4 دقیقه)" + "search_filters_duration_option_short": "کوتاه (< 4 دقیقه)", + "search_filters_title": "پالایه" } diff --git a/locales/fi.json b/locales/fi.json index 84090c24..2aa64ea7 100644 --- a/locales/fi.json +++ b/locales/fi.json @@ -140,7 +140,6 @@ "Show less": "Näytä vähemmän", "Watch on YouTube": "Katso YouTubessa", "Switch Invidious Instance": "Vaihda Invidious-instanssia", - "Broken? Try another Invidious Instance": "Rikki? Kokeile toista Invidious-instanssia", "Hide annotations": "Piilota merkkaukset", "Show annotations": "Näytä merkkaukset", "Genre: ": "Genre: ", @@ -328,33 +327,32 @@ "Videos": "Videot", "Playlists": "Soittolistat", "Community": "Yhteisö", - "relevance": "Osuvuus", - "rating": "Arvostelu", - "date": "Latauspäivämäärä", - "views": "Katselukerrat", - "content_type": "Tyyppi", - "duration": "Kesto", - "features": "Ominaisuudet", - "sort": "Luokittele", - "hour": "Viimeisin tunti", - "today": "Tänään", - "week": "Tämä viikko", - "month": "Tämä kuukausi", - "year": "Tämä vuosi", - "video": "Video", - "channel": "Kanava", - "playlist": "Soittolista", - "movie": "Elokuva", - "show": "Ohjelma", - "hd": "HD", - "subtitles": "Tekstitys/CC", - "creative_commons": "Creative Commons", - "3d": "3D", - "live": "Suora lähetys", - "4k": "4K", - "location": "Sijainti", - "hdr": "HDR", - "filter": "Suodatin", + "search_filters_sort_option_relevance": "Osuvuus", + "search_filters_sort_option_rating": "Arvostelu", + "search_filters_sort_option_date": "Latauspäivämäärä", + "search_filters_sort_option_views": "Katselukerrat", + "search_filters_type_label": "Tyyppi", + "search_filters_duration_label": "Kesto", + "search_filters_features_label": "Ominaisuudet", + "search_filters_sort_label": "Luokittele", + "search_filters_date_option_hour": "Viimeisin tunti", + "search_filters_date_option_today": "Tänään", + "search_filters_date_option_week": "Tämä viikko", + "search_filters_date_option_month": "Tämä kuukausi", + "search_filters_date_option_year": "Tämä vuosi", + "search_filters_type_option_video": "Video", + "search_filters_type_option_channel": "Kanava", + "search_filters_type_option_playlist": "Soittolista", + "search_filters_type_option_movie": "Elokuva", + "search_filters_type_option_show": "Ohjelma", + "search_filters_features_option_hd": "HD", + "search_filters_features_option_subtitles": "Tekstitys/CC", + "search_filters_features_option_c_commons": "Creative Commons", + "search_filters_features_option_three_d": "3D", + "search_filters_features_option_live": "Suora lähetys", + "search_filters_features_option_four_k": "4K", + "search_filters_features_option_location": "Sijainti", + "search_filters_features_option_hdr": "HDR", "Current version: ": "Tämänhetkinen versio: ", "next_steps_error_message": "Sinun tulisi kokeilla seuraavia: ", "next_steps_error_message_refresh": "Päivitä", @@ -390,7 +388,7 @@ "crash_page_before_reporting": "Varmista ennen bugin ilmoittamista, että sinä olet:", "crash_page_refresh": "yrittänyt <a href=\"`x`\">päivittää sivun</a>", "crash_page_read_the_faq": "lukenut <a href=\"`x`\">Usein kysytyt kysymykset (FAQ)</a>", - "crash_page_search_issue": "etsinyt <a href=\"`x`\">olemassa olevia issueita Githubissa</a>", + "crash_page_search_issue": "etsinyt <a href=\"`x`\">olemassa olevia issueita GitHubissa</a>", "generic_views_count": "{{count}} katselu", "generic_views_count_plural": "{{count}} katselua", "preferences_quality_dash_option_720p": "720p", @@ -423,8 +421,8 @@ "preferences_quality_dash_label": "Haluttava DASH-videolaatu: ", "generic_count_years": "{{count}} vuosi", "generic_count_years_plural": "{{count}} vuotta", - "purchased": "Ostettu", - "360": "360°", + "search_filters_features_option_purchased": "Ostettu", + "search_filters_features_option_three_sixty": "360°", "videoinfo_watch_on_youTube": "Katso YouTubessa", "none": "ei mikään", "videoinfo_started_streaming_x_ago": "Striimaaminen aloitettu `x` sitten", @@ -433,14 +431,14 @@ "footer_source_code": "Lähdekoodi", "adminprefs_modified_source_code_url_label": "URL muokattuun lähdekoodirepositoryyn", "Released under the AGPLv3 on Github.": "Julkaistu AGPLv3-lisenssin alla GitHubissa.", - "short": "Lyhyt (< 4 minuuttia)", - "long": "Pitkä (> 20 minuuttia)", + "search_filters_duration_option_short": "Lyhyt (< 4 minuuttia)", + "search_filters_duration_option_long": "Pitkä (> 20 minuuttia)", "footer_documentation": "Dokumentaatio", "footer_original_source_code": "Alkuperäinen lähdekoodi", "footer_modfied_source_code": "Muokattu lähdekoodi", "Japanese (auto-generated)": "Japani (automaattisesti luotu)", "German (auto-generated)": "Saksa (automaattisesti luotu)", - "Portuguese (auto-generated)": "Portugali (automaattisesti luotu)", + "Portuguese (auto-generated)": "portugali (automaattisesti luotu)", "Russian (auto-generated)": "Venäjä (automaattisesti luotu)", "preferences_watch_history_label": "Ota katseluhistoria käyttöön: ", "English (United Kingdom)": "Englanti (Iso-Britannia)", @@ -456,10 +454,21 @@ "Interlingue": "Interlingue", "Italian (auto-generated)": "Italia (automaattisesti luotu)", "Korean (auto-generated)": "Korea (automaattisesti luotu)", - "Portuguese (Brazil)": "Portugali (Brasilia)", + "Portuguese (Brazil)": "portugali (Brasilia)", "Spanish (auto-generated)": "Espanja (automaattisesti luotu)", "Spanish (Mexico)": "Espanja (Meksiko)", "Spanish (Spain)": "Espanja (Espanja)", "Turkish (auto-generated)": "Turkki (automaattisesti luotu)", - "Vietnamese (auto-generated)": "Vietnam (automaattisesti luotu)" + "Vietnamese (auto-generated)": "Vietnam (automaattisesti luotu)", + "search_filters_title": "Suodatin", + "search_message_no_results": "Ei tuloksia löydetty.", + "search_message_change_filters_or_query": "Yritä hakukyselysi laajentamista ja/tai suodattimien muuttamista.", + "search_filters_duration_option_none": "Mikä tahansa kesto", + "search_filters_features_option_vr180": "VR180", + "search_filters_apply_button": "Ota valitut suodattimet käyttöön", + "search_filters_date_label": "Latausaika", + "search_filters_duration_option_medium": "Keskipituinen (4 - 20 minuuttia)", + "search_message_use_another_instance": " Voit myös <a href=\"`x`\">hakea toisella instanssilla</a>.", + "search_filters_date_option_none": "Milloin tahansa", + "search_filters_type_option_all": "Mikä tahansa tyyppi" } diff --git a/locales/fr.json b/locales/fr.json index 96103580..6fee70f9 100644 --- a/locales/fr.json +++ b/locales/fr.json @@ -135,7 +135,7 @@ "subscriptions_unseen_notifs_count_plural": "{{count}} notifications non vues", "search": "rechercher", "Log out": "Se déconnecter", - "Released under the AGPLv3 on Github.": "Publié sous licence AGPLv3 sur Github.", + "Released under the AGPLv3 on Github.": "Publié sous licence AGPLv3 sur GitHub.", "Source available here.": "Code source disponible ici.", "View JavaScript license information.": "Informations des licences JavaScript.", "View privacy policy.": "Politique de confidentialité.", @@ -155,7 +155,6 @@ "Show less": "Afficher moins", "Watch on YouTube": "Voir la vidéo sur Youtube", "Switch Invidious Instance": "Changer d'instance", - "Broken? Try another Invidious Instance": "Instance Invidious défectueuse ? Essayez-en une autre", "Hide annotations": "Masquer les annotations", "Show annotations": "Afficher les annotations", "Genre: ": "Genre : ", @@ -361,33 +360,32 @@ "Videos": "Vidéos", "Playlists": "Listes de lecture", "Community": "Communauté", - "relevance": "pertinence", - "rating": "évaluation", - "date": "date", - "views": "nombre de vues", - "content_type": "type", - "duration": "durée", - "features": "fonctionnalités", - "sort": "Trier par", - "hour": "dernière heure", - "today": "aujourd'hui", - "week": "semaine", - "month": "mois", - "year": "année", - "video": "vidéo", - "channel": "chaîne", - "playlist": "liste de lecture", - "movie": "film", - "show": "émission", - "hd": "HD", - "subtitles": "sous-titres / CC", - "creative_commons": "Creative Commons", - "3d": "3D", - "live": "en direct", - "4k": "4K", - "location": "emplacement", - "hdr": "HDR", - "filter": "filtrer", + "search_filters_sort_option_relevance": "Pertinence", + "search_filters_sort_option_rating": "Notation", + "search_filters_sort_option_date": "Date d'ajout", + "search_filters_sort_option_views": "Nombre de vues", + "search_filters_type_label": "Type de contenu", + "search_filters_duration_label": "Durée", + "search_filters_features_label": "Fonctionnalités", + "search_filters_sort_label": "Trier par", + "search_filters_date_option_hour": "Dernière heure", + "search_filters_date_option_today": "Aujourd'hui", + "search_filters_date_option_week": "Cette semaine", + "search_filters_date_option_month": "Ce mois-ci", + "search_filters_date_option_year": "Cette année", + "search_filters_type_option_video": "Vidéo", + "search_filters_type_option_channel": "Chaîne", + "search_filters_type_option_playlist": "Liste de lecture", + "search_filters_type_option_movie": "Film", + "search_filters_type_option_show": "Émission", + "search_filters_features_option_hd": "HD", + "search_filters_features_option_subtitles": "Sous-titres (CC)", + "search_filters_features_option_c_commons": "Creative Commons", + "search_filters_features_option_three_d": "3D", + "search_filters_features_option_live": "En direct", + "search_filters_features_option_four_k": "4K", + "search_filters_features_option_location": "emplacement", + "search_filters_features_option_hdr": "HDR", "Current version: ": "Version actuelle : ", "next_steps_error_message": "Vous pouvez essayer de : ", "next_steps_error_message_refresh": "Rafraîchir la page", @@ -397,8 +395,8 @@ "preferences_region_label": "Pays du contenu : ", "footer_donate_page": "Faire un don", "footer_modfied_source_code": "Code source modifié", - "short": "Courte (< 4 minutes)", - "long": "Longue (> 20 minutes)", + "search_filters_duration_option_short": "Courte (< 4 minutes)", + "search_filters_duration_option_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", @@ -415,12 +413,12 @@ "preferences_quality_dash_option_240p": "240p", "preferences_quality_dash_option_144p": "144p", "invidious": "Invidious", - "360": "360°", + "search_filters_features_option_three_sixty": "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", + "search_filters_features_option_purchased": "Acheté", "videoinfo_invidious_embed_link": "Lien intégré", "download_subtitles": "Sous-titres - `x` (.vtt)", "user_saved_playlists": "`x` listes de lecture sauvegardées", @@ -435,7 +433,7 @@ "crash_page_refresh": "tenté de <a href=\"`x`\">rafraîchir la page</a>", "crash_page_switch_instance": "essayé d'<a href=\"`x`\">utiliser une autre instance</a>", "crash_page_read_the_faq": "lu la <a href=\"`x`\">Foire Aux Questions (FAQ)</a>", - "crash_page_search_issue": "<a href=\"`x`\">cherché ce bug sur Github</a>", + "crash_page_search_issue": "<a href=\"`x`\">cherché ce bug sur GitHub</a>", "crash_page_before_reporting": "Avant de signaler un bug, veuillez vous assurez que vous avez :", "crash_page_report_issue": "Si aucune des solutions proposées ci-dessus ne vous a aidé, veuillez <a href=\"`x`\">ouvrir une \"issue\" sur GitHub</a> (de préférence en anglais) et d'y inclure le message suivant (ne PAS traduire le texte) :", "English (United States)": "Anglais (Etats-Unis)", @@ -461,5 +459,16 @@ "Vietnamese (auto-generated)": "Vietnamien (auto-généré)", "Russian (auto-generated)": "Russe (auto-généré)", "Spanish (Spain)": "Espagnol (Espagne)", - "preferences_watch_history_label": "Activer l'historique de visionnage : " + "preferences_watch_history_label": "Activer l'historique de visionnage : ", + "search_filters_title": "Filtres", + "search_message_change_filters_or_query": "Essayez d'élargir votre recherche et/ou de changer les filtres.", + "search_filters_date_option_none": "Toutes les dates", + "search_filters_duration_option_medium": "Moyenne (de 4 à 20 minutes)", + "search_filters_apply_button": "Appliquer les filtres", + "search_message_no_results": "Aucun résultat.", + "search_message_use_another_instance": " Vous pouvez également <a href=\"`x`\">effectuer votre recherche sur une autre instance</a>.", + "search_filters_type_option_all": "Tous les types", + "search_filters_date_label": "Date d'ajout", + "search_filters_features_option_vr180": "VR180", + "search_filters_duration_option_none": "Toutes les durées" } diff --git a/locales/he.json b/locales/he.json index 2c9258b9..384b2657 100644 --- a/locales/he.json +++ b/locales/he.json @@ -274,32 +274,32 @@ "Videos": "סרטונים", "Playlists": "פלייליסטים", "Community": "קהילה", - "relevance": "רלוונטיות", - "rating": "דירוג", - "date": "תאריך העלאה", - "views": "מספר צפיות", - "content_type": "סוג", - "duration": "משך זמן", - "features": "תכונות", - "sort": "מיון לפי", - "hour": "השעה האחרונה", - "today": "היום", - "week": "השבוע", - "month": "החודש", - "year": "השנה", - "video": "סרטון", - "channel": "ערוץ", - "playlist": "פלייליסט", - "movie": "סרט", - "show": "תכנית טלוויזיה", - "hd": "HD", - "subtitles": "כתוביות", - "creative_commons": "Creative Commons", - "3d": "3D", - "live": "Live", - "4k": "4K", - "location": "מיקום", - "hdr": "HDR", - "filter": "סינון", - "Current version: ": "הגרסה הנוכחית: " + "search_filters_sort_option_relevance": "רלוונטיות", + "search_filters_sort_option_rating": "דירוג", + "search_filters_sort_option_date": "תאריך העלאה", + "search_filters_sort_option_views": "מספר צפיות", + "search_filters_type_label": "סוג", + "search_filters_duration_label": "משך זמן", + "search_filters_features_label": "תכונות", + "search_filters_sort_label": "מיון לפי", + "search_filters_date_option_hour": "השעה האחרונה", + "search_filters_date_option_today": "היום", + "search_filters_date_option_week": "השבוע", + "search_filters_date_option_month": "החודש", + "search_filters_date_option_year": "השנה", + "search_filters_type_option_video": "סרטון", + "search_filters_type_option_channel": "ערוץ", + "search_filters_type_option_playlist": "פלייליסט", + "search_filters_type_option_movie": "סרט", + "search_filters_type_option_show": "תכנית טלוויזיה", + "search_filters_features_option_hd": "HD", + "search_filters_features_option_subtitles": "כתוביות", + "search_filters_features_option_c_commons": "Creative Commons", + "search_filters_features_option_three_d": "3D", + "search_filters_features_option_live": "Live", + "search_filters_features_option_four_k": "4K", + "search_filters_features_option_location": "מיקום", + "search_filters_features_option_hdr": "HDR", + "Current version: ": "הגרסה הנוכחית: ", + "search_filters_title": "סינון" } diff --git a/locales/hr.json b/locales/hr.json index 688368d2..94633aac 100644 --- a/locales/hr.json +++ b/locales/hr.json @@ -121,7 +121,7 @@ "Subscriptions": "Pretplate", "search": "traži", "Log out": "Odjavi se", - "Released under the AGPLv3 on Github.": "Izdano pod licencom AGPLv3 na Github-u.", + "Released under the AGPLv3 on Github.": "Izdano pod licencom AGPLv3 na GitHub-u.", "Source available here.": "Izvor je ovdje dostupan.", "View JavaScript license information.": "Prikaži informacije o JavaScript licenci.", "View privacy policy.": "Prikaži politiku privatnosti.", @@ -141,7 +141,6 @@ "Show less": "Pokaži manje", "Watch on YouTube": "Gledaj na YouTubeu", "Switch Invidious Instance": "Promijeni Invidious instancu", - "Broken? Try another Invidious Instance": "Pokvarena? Probaj jednu drugu Invidious instancu", "Hide annotations": "Sakrij napomene", "Show annotations": "Prikaži napomene", "Genre: ": "Žanr: ", @@ -329,41 +328,40 @@ "Videos": "Videa", "Playlists": "Zbirke", "Community": "Zajednica", - "relevance": "značaj", - "rating": "ocjena", - "date": "datum", - "views": "prikazi", - "content_type": "vrsta_sadržaja", - "duration": "trajanje", - "features": "funkcije", - "sort": "redoslijed", - "hour": "sat", - "today": "danas", - "week": "tjedan", - "month": "mjesec", - "year": "godina", - "video": "video", - "channel": "kanal", - "playlist": "Zbirka", - "movie": "film", - "show": "emisija", - "hd": "hd", - "subtitles": "titlovi", - "creative_commons": "creative_commons", - "3d": "3d", - "live": "uživo", - "4k": "4k", - "location": "lokacija", - "hdr": "hdr", - "filter": "filtar", + "search_filters_sort_option_relevance": "značaj", + "search_filters_sort_option_rating": "ocjena", + "search_filters_sort_option_date": "datum", + "search_filters_sort_option_views": "prikazi", + "search_filters_type_label": "vrsta_sadržaja", + "search_filters_duration_label": "trajanje", + "search_filters_features_label": "funkcije", + "search_filters_sort_label": "redoslijed", + "search_filters_date_option_hour": "sat", + "search_filters_date_option_today": "danas", + "search_filters_date_option_week": "tjedan", + "search_filters_date_option_month": "mjesec", + "search_filters_date_option_year": "godina", + "search_filters_type_option_video": "video", + "search_filters_type_option_channel": "kanal", + "search_filters_type_option_playlist": "Zbirka", + "search_filters_type_option_movie": "film", + "search_filters_type_option_show": "emisija", + "search_filters_features_option_hd": "hd", + "search_filters_features_option_subtitles": "titlovi", + "search_filters_features_option_c_commons": "creative_commons", + "search_filters_features_option_three_d": "3d", + "search_filters_features_option_live": "uživo", + "search_filters_features_option_four_k": "4k", + "search_filters_features_option_location": "lokacija", + "search_filters_features_option_hdr": "hdr", "Current version: ": "Trenutačna verzija: ", "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", "adminprefs_modified_source_code_url_label": "URL do repozitorija izmijenjenog izvornog koda", - "short": "Kratki (< 4 minute)", - "long": "Dugi (> 20 minute)", + "search_filters_duration_option_short": "Kratki (< 4 minute)", + "search_filters_duration_option_long": "Dugi (> 20 minute)", "footer_source_code": "Izvorni kod", "footer_modfied_source_code": "Izmijenjeni izvorni kod", "footer_documentation": "Dokumentacija", @@ -382,8 +380,8 @@ "preferences_quality_dash_option_240p": "240 p", "preferences_quality_dash_option_144p": "144 p", "invidious": "Invidious", - "purchased": "Kupljeno", - "360": "360 °", + "search_filters_features_option_purchased": "Kupljeno", + "search_filters_features_option_three_sixty": "360 °", "none": "bez", "videoinfo_youTube_embed_link": "Ugradi", "user_created_playlists": "`x` stvorene zbirke", @@ -452,7 +450,7 @@ "crash_page_refresh": "pokušaj <a href=\"`x`\">aktualizirati stranicu</a>", "crash_page_switch_instance": "pokušaj <a href=\"`x`\">koristiti jednu drugu instancu</a>", "crash_page_read_the_faq": "pročitaj <a href=\"`x`\">Često postavljena pitanja (ČPP)</a>", - "crash_page_search_issue": "pretraži <a href=\"`x`\">postojeće probleme na Github-u</a>", + "crash_page_search_issue": "pretraži <a href=\"`x`\">postojeće probleme na GitHub-u</a>", "crash_page_report_issue": "Ako ništa od gore navedenog ne pomaže, <a href=\"`x`\">prijavi novi problem na GitHub-u</a> (po mogućnosti na engleskom) i uključi sljedeći tekst u poruku (NEMOJ prevoditi taj tekst):", "English (United Kingdom)": "Engleski (Ujedinjeno Kraljevstvo)", "English (United States)": "Engleski (Sjedinjene Američke Države)", @@ -477,5 +475,6 @@ "Korean (auto-generated)": "Korejski (automatski generiran)", "Portuguese (auto-generated)": "Portugalski (automatski generiran)", "Spanish (auto-generated)": "Španjolski (automatski generiran)", - "preferences_watch_history_label": "Aktiviraj povijest gledanja: " + "preferences_watch_history_label": "Aktiviraj povijest gledanja: ", + "search_filters_title": "Filtar" } diff --git a/locales/hu-HU.json b/locales/hu-HU.json index d1948a47..a3679813 100644 --- a/locales/hu-HU.json +++ b/locales/hu-HU.json @@ -365,9 +365,9 @@ "preferences_quality_dash_option_144p": "144p", "invidious": "Invidious", "videoinfo_started_streaming_x_ago": "`x` ezelőtt kezdte streamelni", - "views": "Mennyien látták", - "purchased": "Megvásárolva", - "360": "360°-os", + "search_filters_sort_option_views": "Mennyien látták", + "search_filters_features_option_purchased": "Megvásárolva", + "search_filters_features_option_three_sixty": "360°-os", "footer_original_source_code": "Eredeti forráskód", "none": "egyik sem", "videoinfo_watch_on_youTube": "YouTube-on megnézni", @@ -382,14 +382,13 @@ "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", + "search_filters_sort_option_date": "Feltöltés dátuma", "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.": "AGPLv3 licenc alapján a GitHubon", - "3d": "3D-ben", - "live": "Élőben", - "filter": "Szűrők", + "search_filters_features_option_three_d": "3D-ben", + "search_filters_features_option_live": "Élőben", "next_steps_error_message_refresh": "Újratöltés", "footer_donate_page": "Adakozás", "footer_source_code": "Forráskód", @@ -397,40 +396,39 @@ "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", - "rating": "Pontszám", - "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", + "search_filters_sort_option_relevance": "Relevancia", + "search_filters_sort_option_rating": "Pontszám", + "search_filters_type_label": "Típus", + "search_filters_date_option_today": "Mai napon", + "search_filters_type_option_channel": "Csatorna", + "search_filters_type_option_video": "Videó", + "search_filters_type_option_playlist": "Lejátszási lista", + "search_filters_features_option_c_commons": "Creative Commons", + "search_filters_features_label": "Jellemzők", + "search_filters_sort_label": "Rendezés módja", "preferences_category_misc": "További beállítások", "%A %B %-d, %Y": "%Y. %B %-d %A", - "long": "Hosszú (20 percnél hosszabb)", - "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ő", + "search_filters_duration_option_long": "Hosszú (20 percnél hosszabb)", + "search_filters_date_option_year": "Ebben az évben", + "search_filters_date_option_hour": "Az elmúlt órában", + "search_filters_type_option_movie": "Film", + "search_filters_features_option_hdr": "HDR", + "search_filters_duration_label": "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", + "search_filters_date_option_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", + "search_filters_features_option_hd": "HD", "next_steps_error_message_go_to_youtube": "Ugrás a YouTube-ra", - "show": "Műsor", - "4k": "4K", - "short": "Rövid (4 percnél nem több)", - "month": "Ebben a hónapban", - "subtitles": "Felirattal", - "location": "Közelben", + "search_filters_type_option_show": "Műsor", + "search_filters_features_option_four_k": "4K", + "search_filters_duration_option_short": "Rövid (4 percnél nem több)", + "search_filters_date_option_month": "Ebben a hónapban", + "search_filters_features_option_subtitles": "Felirattal", + "search_filters_features_option_location": "Közelben", "crash_page_you_found_a_bug": "Úgy néz ki, találtál egy hibát az Invidiousban.", "crash_page_before_reporting": "Mielőtt jelentenéd a hibát:", "crash_page_read_the_faq": "olvasd el a <a href=\"`x`\">Gyakran Ismételt Kérdéseket (GYIK)</a>", @@ -460,5 +458,6 @@ "Italian (auto-generated)": "olasz (automatikusan generált)", "Dutch (auto-generated)": "holland (automatikusan generált)", "French (auto-generated)": "francia (automatikusan generált)", - "Vietnamese (auto-generated)": "vietnámi (automatikusan generált)" + "Vietnamese (auto-generated)": "vietnámi (automatikusan generált)", + "search_filters_title": "Szűrők" } diff --git a/locales/id.json b/locales/id.json index be15e8e1..71b7bdb1 100644 --- a/locales/id.json +++ b/locales/id.json @@ -26,15 +26,15 @@ "No": "Tidak", "Import and Export Data": "Impor dan Ekspor Data", "Import": "Impor", - "Import Invidious data": "Impor data Invidious", - "Import YouTube subscriptions": "Impor langganan YouTube", + "Import Invidious data": "Impor JSON data Invidious", + "Import YouTube subscriptions": "Impor langganan YouTube/OPML", "Import FreeTube subscriptions (.db)": "Impor langganan FreeTube (.db)", "Import NewPipe subscriptions (.json)": "Impor langganan NewPipe (.json)", "Import NewPipe data (.zip)": "Impor data NewPipe (.zip)", "Export": "Ekspor", "Export subscriptions as OPML": "Ekspor langganan sebagai OPML", "Export subscriptions as OPML (for NewPipe & FreeTube)": "Ekspor langganan sebagai OPML (untuk NewPipe & FreeTube)", - "Export data as JSON": "Ekspor data sebagai JSON", + "Export data as JSON": "Ekspor data Invidious sebagai JSON", "Delete account?": "Hapus akun?", "History": "Riwayat", "An alternative front-end to YouTube": "Sebuah alternatif layar depan untuk YouTube", @@ -71,7 +71,7 @@ "preferences_related_videos_label": "Tampilkan video terkait: ", "preferences_annotations_label": "Tampilkan anotasi secara baku: ", "preferences_extend_desc_label": "Perluas deskripsi video secara otomatis: ", - "preferences_vr_mode_label": "Video interaktif 360°: ", + "preferences_vr_mode_label": "Video interaktif 360° (memerlukan WebGL): ", "preferences_category_visual": "Preferensi visual", "preferences_player_style_label": "Gaya pemutar: ", "Dark mode: ": "Mode gelap: ", @@ -128,7 +128,7 @@ "subscriptions_unseen_notifs_count_0": "{{count}} pemberitahuan belum dilihat", "search": "cari", "Log out": "Keluar", - "Released under the AGPLv3 on Github.": "Dirilis di bawah AGPLv3 di Github.", + "Released under the AGPLv3 on Github.": "Dirilis di bawah AGPLv3 di GitHub.", "Source available here.": "Sumber tersedia di sini.", "View JavaScript license information.": "Tampilkan informasi lisensi JavaScript.", "View privacy policy.": "Lihat kebijakan privasi.", @@ -148,7 +148,6 @@ "Show less": "Tampilkan lebih sedikit", "Watch on YouTube": "Tonton di YouTube", "Switch Invidious Instance": "Ganti peladen Invidious", - "Broken? Try another Invidious Instance": "Rusak? Coba peladen Invidious yang lain", "Hide annotations": "Sembunyikan anotasi", "Show annotations": "Tampilkan anotasi", "Genre: ": "Genre: ", @@ -345,33 +344,32 @@ "Videos": "Video", "Playlists": "Daftar putar", "Community": "Komunitas", - "relevance": "Relevansi", - "rating": "Penilaian", - "date": "Tanggal unggah", - "views": "Jumlah ditonton", - "content_type": "Tipe", - "duration": "Durasi", - "features": "Fitur", - "sort": "Urut Berdasarkan", - "hour": "Jam Terakhir", - "today": "Hari Ini", - "week": "Pekan Ini", - "month": "Bulan Ini", - "year": "Tahun Ini", - "video": "Video", - "channel": "Kanal", - "playlist": "Daftar Putar", - "movie": "Film", - "show": "Pertunjukan/Acara", - "hd": "HD", - "subtitles": "Takarir", - "creative_commons": "Creative Commons", - "3d": "3D", - "live": "Siaran Langsung", - "4k": "4K", - "location": "Lokasi", - "hdr": "HDR", - "filter": "Saring", + "search_filters_sort_option_relevance": "Relevansi", + "search_filters_sort_option_rating": "Penilaian", + "search_filters_sort_option_date": "Tanggal unggah", + "search_filters_sort_option_views": "Jumlah ditonton", + "search_filters_type_label": "Tipe", + "search_filters_duration_label": "Durasi", + "search_filters_features_label": "Fitur", + "search_filters_sort_label": "Urut Berdasarkan", + "search_filters_date_option_hour": "Jam Terakhir", + "search_filters_date_option_today": "Hari Ini", + "search_filters_date_option_week": "Pekan Ini", + "search_filters_date_option_month": "Bulan Ini", + "search_filters_date_option_year": "Tahun Ini", + "search_filters_type_option_video": "Video", + "search_filters_type_option_channel": "Kanal", + "search_filters_type_option_playlist": "Daftar Putar", + "search_filters_type_option_movie": "Film", + "search_filters_type_option_show": "Pertunjukan/Acara", + "search_filters_features_option_hd": "HD", + "search_filters_features_option_subtitles": "Takarir", + "search_filters_features_option_c_commons": "Creative Commons", + "search_filters_features_option_three_d": "3D", + "search_filters_features_option_live": "Siaran Langsung", + "search_filters_features_option_four_k": "4K", + "search_filters_features_option_location": "Lokasi", + "search_filters_features_option_hdr": "HDR", "Current version: ": "Versi saat ini: ", "next_steps_error_message": "Setelah itu Anda harus mencoba: ", "next_steps_error_message_refresh": "Segarkan", @@ -380,8 +378,8 @@ "adminprefs_modified_source_code_url_label": "URL ke repositori kode sumber yang dimodifikasi", "footer_source_code": "Kode sumber", "footer_original_source_code": "Kode sumber yang asli", - "short": "Pendek (< 4 menit)", - "long": "Panjang (> 20 menit)", + "search_filters_duration_option_short": "Pendek (< 4 menit)", + "search_filters_duration_option_long": "Panjang (> 20 menit)", "footer_modfied_source_code": "Kode sumber yang dimodifikasi", "footer_documentation": "Dokumentasi", "preferences_region_label": "Konten dari negara: ", @@ -398,8 +396,8 @@ "preferences_quality_dash_option_240p": "240p", "preferences_quality_dash_option_144p": "144p", "invidious": "Invidious", - "purchased": "Dibeli", - "360": "360°", + "search_filters_features_option_purchased": "Dibeli", + "search_filters_features_option_three_sixty": "360°", "none": "tidak ada", "videoinfo_watch_on_youTube": "Tonton di YouTube", "videoinfo_youTube_embed_link": "Tersemat", @@ -416,5 +414,9 @@ "Video unavailable": "Video tidak tersedia", "preferences_save_player_pos_label": "Simpan posisi pemutaran: ", "crash_page_you_found_a_bug": "Sepertinya kamu telah menemukan masalah di invidious!", - "crash_page_before_reporting": "Sebelum melaporkan masalah, pastikan anda memiliki:" + "crash_page_before_reporting": "Sebelum melaporkan masalah, pastikan anda memiliki:", + "English (United States)": "Inggris (US)", + "preferences_watch_history_label": "Aktifkan riwayat tontonan: ", + "English (United Kingdom)": "Inggris (UK)", + "search_filters_title": "Saring" } diff --git a/locales/it.json b/locales/it.json index 411148c9..7ba5ff2d 100644 --- a/locales/it.json +++ b/locales/it.json @@ -27,7 +27,7 @@ "No": "No", "Import and Export Data": "Importazione ed esportazione dati", "Import": "Importa", - "Import Invidious data": "Importa dati Invidious", + "Import Invidious data": "Importa dati Invidious in formato JSON", "Import YouTube subscriptions": "Importa le iscrizioni da YouTube", "Import FreeTube subscriptions (.db)": "Importa le iscrizioni da FreeTube (.db)", "Import NewPipe subscriptions (.json)": "Importa le iscrizioni da NewPipe (.json)", @@ -35,7 +35,7 @@ "Export": "Esporta", "Export subscriptions as OPML": "Esporta gli abbonamenti come OPML", "Export subscriptions as OPML (for NewPipe & FreeTube)": "Esporta gli abbonamenti come OPML (per NewPipe e FreeTube)", - "Export data as JSON": "Esporta i dati in formato JSON", + "Export data as JSON": "Esporta i dati Invidious in formato JSON", "Delete account?": "Eliminare l'account?", "History": "Cronologia", "An alternative front-end to YouTube": "Un'interfaccia alternativa per YouTube", @@ -347,32 +347,31 @@ "Videos": "Video", "Playlists": "Playlist", "Community": "Comunità", - "relevance": "Pertinenza", - "rating": "Valutazione", - "date": "Data di caricamento", - "views": "Numero di visualizzazioni", - "content_type": "Tipo", - "duration": "Durata", - "features": "Caratteristiche", - "sort": "Ordina per", - "hour": "Ultima ora", - "today": "Oggi", - "week": "Questa settimana", - "month": "Questo mese", - "year": "Quest'anno", - "video": "Video", - "channel": "Canale", - "playlist": "Playlist", - "movie": "Film", - "hd": "AD", - "subtitles": "Sottotitoli / CC", - "creative_commons": "Creative Commons", - "3d": "3D", - "live": "In diretta", - "4k": "4K", - "location": "Posizione", - "hdr": "HDR", - "filter": "Filtra", + "search_filters_sort_option_relevance": "Pertinenza", + "search_filters_sort_option_rating": "Valutazione", + "search_filters_sort_option_date": "Data di caricamento", + "search_filters_sort_option_views": "Numero di visualizzazioni", + "search_filters_type_label": "Tipo", + "search_filters_duration_label": "Durata", + "search_filters_features_label": "Caratteristiche", + "search_filters_sort_label": "Ordina per", + "search_filters_date_option_hour": "Ultima ora", + "search_filters_date_option_today": "Oggi", + "search_filters_date_option_week": "Questa settimana", + "search_filters_date_option_month": "Questo mese", + "search_filters_date_option_year": "Quest'anno", + "search_filters_type_option_video": "Video", + "search_filters_type_option_channel": "Canale", + "search_filters_type_option_playlist": "Playlist", + "search_filters_type_option_movie": "Film", + "search_filters_features_option_hd": "AD", + "search_filters_features_option_subtitles": "Sottotitoli / CC", + "search_filters_features_option_c_commons": "Creative Commons", + "search_filters_features_option_three_d": "3D", + "search_filters_features_option_live": "In diretta", + "search_filters_features_option_four_k": "4K", + "search_filters_features_option_location": "Posizione", + "search_filters_features_option_hdr": "HDR", "Current version: ": "Versione attuale: ", "preferences_quality_dash_option_240p": "240p", "preferences_quality_dash_option_360p": "360p", @@ -382,9 +381,9 @@ "preferences_quality_dash_option_1440p": "1440p", "preferences_quality_dash_option_2160p": "2160p", "preferences_quality_dash_option_4320p": "4320p", - "360": "360°", + "search_filters_features_option_three_sixty": "360°", "preferences_quality_dash_option_144p": "144p", - "Released under the AGPLv3 on Github.": "Rilasciato su Github con licenza AGPLv3.", + "Released under the AGPLv3 on Github.": "Rilasciato su GitHub con licenza AGPLv3.", "preferences_quality_option_medium": "Media", "preferences_quality_option_small": "Piccola", "preferences_quality_dash_option_best": "Migliore", @@ -409,22 +408,22 @@ "preferences_automatic_instance_redirect_label": "Reindirizzamento automatico dell'istanza (ripiego su redirect.invidious.io): ", "Video unavailable": "Video non disponibile", "preferences_show_nick_label": "Mostra nickname in alto: ", - "short": "Corto (< 4 minuti)", "videoinfo_youTube_embed_link": "Incorpora", "videoinfo_invidious_embed_link": "Incorpora collegamento", "user_created_playlists": "playlist create da `x`", "preferences_save_player_pos_label": "Memorizza il minutaggio raggiunto dal video: ", - "purchased": "Acquistato", "preferences_quality_option_dash": "DASH (qualità adattiva)", "preferences_region_label": "Nazione del contenuto: ", "preferences_category_misc": "Preferenze varie", - "show": "Serie", - "long": "Lungo (> 20 minuti)", "next_steps_error_message": "Dopodiché dovresti provare a: ", "next_steps_error_message_refresh": "Aggiornare", "footer_donate_page": "Dona", "footer_source_code": "Codice sorgente", "adminprefs_modified_source_code_url_label": "Link per il repository del codice sorgente modificato", "Show more": "Mostra di più", - "Broken? Try another Invidious Instance": "Non funzionante? Prova un’altra istanza Invidious" + "search_filters_title": "Filtra", + "search_filters_type_option_show": "Serie", + "search_filters_duration_option_short": "Corto (< 4 minuti)", + "search_filters_duration_option_long": "Lungo (> 20 minuti)", + "search_filters_features_option_purchased": "Acquistato" } diff --git a/locales/ja.json b/locales/ja.json index 9708c0ea..20d3c20e 100644 --- a/locales/ja.json +++ b/locales/ja.json @@ -148,7 +148,6 @@ "Show less": "表示を減らす", "Watch on YouTube": "YouTube で視聴", "Switch Invidious Instance": "Invidiousインスタンスの変更", - "Broken? Try another Invidious Instance": "壊れる?違うInvidiousインスタンスを試してみる", "Hide annotations": "アノテーションを隠す", "Show annotations": "アノテーションを表示", "Genre: ": "ジャンル: ", @@ -345,44 +344,43 @@ "Videos": "動画", "Playlists": "プレイリスト", "Community": "コミュニティ", - "relevance": "関連", - "rating": "評価", - "date": "時刻", - "views": "再生回数", - "content_type": "コンテンツの種類", - "duration": "再生時間", - "features": "機能", - "sort": "順番", - "hour": "1時間前", - "today": "今日", - "week": "今週", - "month": "今月", - "year": "今年", - "video": "動画", - "channel": "チャンネル", - "playlist": "再生リスト", - "movie": "映画", - "show": "番組", - "hd": "HD", - "subtitles": "字幕", - "creative_commons": "クリエイティブ・コモンズ", - "3d": "3D", - "live": "生配信", - "4k": "4K", - "location": "場所", - "hdr": "HDR", - "filter": "フィルタ", + "search_filters_sort_option_relevance": "関連", + "search_filters_sort_option_rating": "評価", + "search_filters_sort_option_date": "時刻", + "search_filters_sort_option_views": "再生回数", + "search_filters_type_label": "コンテンツの種類", + "search_filters_duration_label": "再生時間", + "search_filters_features_label": "機能", + "search_filters_sort_label": "順番", + "search_filters_date_option_hour": "1時間前", + "search_filters_date_option_today": "今日", + "search_filters_date_option_week": "今週", + "search_filters_date_option_month": "今月", + "search_filters_date_option_year": "今年", + "search_filters_type_option_video": "動画", + "search_filters_type_option_channel": "チャンネル", + "search_filters_type_option_playlist": "再生リスト", + "search_filters_type_option_movie": "映画", + "search_filters_type_option_show": "番組", + "search_filters_features_option_hd": "HD", + "search_filters_features_option_subtitles": "字幕", + "search_filters_features_option_c_commons": "クリエイティブ・コモンズ", + "search_filters_features_option_three_d": "3D", + "search_filters_features_option_live": "生配信", + "search_filters_features_option_four_k": "4K", + "search_filters_features_option_location": "場所", + "search_filters_features_option_hdr": "HDR", "Current version: ": "現在のバージョン: ", "next_steps_error_message": "下記のものを試して下さい: ", "next_steps_error_message_refresh": "再読込", "next_steps_error_message_go_to_youtube": "YouTubeへ", - "short": "4 分未満", + "search_filters_duration_option_short": "4 分未満", "footer_documentation": "文書", "footer_source_code": "ソースコード", "footer_original_source_code": "ソースコード(元)", "footer_modfied_source_code": "ソースコード(編集)", "adminprefs_modified_source_code_url_label": "編集したソースコードのレポジトリーURL", - "long": "20 分以上", + "search_filters_duration_option_long": "20 分以上", "preferences_region_label": "地域: ", "footer_donate_page": "寄付する", "preferences_quality_dash_label": "優先するDash画質 : ", @@ -404,7 +402,7 @@ "videoinfo_invidious_embed_link": "埋め込みリンク", "none": "なし", "download_subtitles": "字幕 - `x` (.vtt)", - "purchased": "購入済み", + "search_filters_features_option_purchased": "購入済み", "preferences_quality_option_dash": "DASH (適切な品質)", "preferences_quality_dash_option_worst": "最悪", "preferences_quality_dash_option_best": "最高", @@ -434,5 +432,6 @@ "Spanish (Mexico)": "スペイン語 (メキシコ)", "Spanish (Spain)": "スペイン語 (スペイン)", "Vietnamese (auto-generated)": "ベトナム語 (自動生成)", - "360": "360°" + "search_filters_title": "フィルタ", + "search_filters_features_option_three_sixty": "360°" } diff --git a/locales/ko.json b/locales/ko.json index a579fe56..12c2b31f 100644 --- a/locales/ko.json +++ b/locales/ko.json @@ -86,7 +86,7 @@ "generic_playlists_count_0": "{{count}} 재생목록", "generic_subscribers_count_0": "{{count}} 구독자", "generic_subscriptions_count_0": "{{count}} 구독", - "playlist": "재생목록", + "search_filters_type_option_playlist": "재생목록", "Korean": "한국어", "Japanese": "일본어", "Greek": "그리스어", @@ -129,7 +129,7 @@ "Delete playlist": "재생목록 삭제", "Delete playlist `x`?": "재생목록 `x` 를 삭제 하시겠습니까?", "Updated `x` ago": "`x` 전에 업데이트됨", - "Released under the AGPLv3 on Github.": "Github에 AGPLv3 으로 배포됩니다.", + "Released under the AGPLv3 on Github.": "GitHub에 AGPLv3 으로 배포됩니다.", "View all playlists": "모든 재생목록 보기", "Private": "비공개", "Unlisted": "목록에 없음", @@ -195,16 +195,15 @@ "Maori": "마오리어", "Maltese": "몰타어", "Wrong answer": "잘못된 답변", - "live": "실시간", - "3d": "3D", - "location": "지역", - "4k": "4K", - "filter": "필터", - "hdr": "HDR", + "search_filters_features_option_live": "실시간", + "search_filters_features_option_three_d": "3D", + "search_filters_features_option_location": "지역", + "search_filters_features_option_four_k": "4K", + "search_filters_features_option_hdr": "HDR", "Current version: ": "현재 버전: ", "next_steps_error_message_refresh": "새로 고침", "next_steps_error_message_go_to_youtube": "YouTube로 가기", - "subtitles": "자막", + "search_filters_features_option_subtitles": "자막", "`x` marked it with a ❤": "`x`님의 ❤", "Download as: ": "다음으로 다운로드: ", "Download": "다운로드", @@ -219,7 +218,7 @@ "Latvian": "라트비아어", "Latin": "라틴어", "Lao": "라오어", - "channel": "채널", + "search_filters_type_option_channel": "채널", "Kyrgyz": "키르기스어", "Kurdish": "쿠르드어", "Khmer": "크메르어", @@ -279,7 +278,7 @@ "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가 꺼져 있는 것 같습니다! 댓글을 보려면 여기를 클릭하세요. 댓글을 로드하는 데 시간이 조금 더 걸릴 수 있습니다.", "Shared `x`": "공유된 `x`", "Whitelisted regions: ": "차단되지 않은 지역: ", - "views": "조회수", + "search_filters_sort_option_views": "조회수", "Please log in": "로그인하세요", "Password cannot be longer than 55 characters": "비밀번호는 55자 이하여야 합니다", "Password cannot be empty": "비밀번호는 비워둘 수 없습니다", @@ -298,7 +297,6 @@ "Empty playlist": "재생목록 비어 있음", "Show annotations": "주석 보이기", "Hide annotations": "주석 숨기기", - "Broken? Try another Invidious Instance": "안되나요? 다른 Invidious 인스턴스를 시도해보세요", "Switch Invidious Instance": "Invidious 인스턴스 변경", "Spanish": "스페인어", "Southern Sotho": "소토어", @@ -343,12 +341,12 @@ "Premieres `x`": "최초 공개 `x`", "Premieres in `x`": "`x` 에 최초 공개", "next_steps_error_message": "다음 방법을 시도해 보세요: ", - "creative_commons": "크리에이티브 커먼즈", - "duration": "길이", - "content_type": "구분", - "date": "업로드 날짜", - "rating": "평점", - "relevance": "관련성", + "search_filters_features_option_c_commons": "크리에이티브 커먼즈", + "search_filters_duration_label": "길이", + "search_filters_type_label": "구분", + "search_filters_sort_option_date": "업로드 날짜", + "search_filters_sort_option_rating": "평점", + "search_filters_sort_option_relevance": "관련성", "Community": "커뮤니티", "Videos": "동영상", "Video mode": "비디오 모드", @@ -365,22 +363,23 @@ "Rating: ": "평점: ", "About": "정보", "Top": "최고", - "hd": "HD", - "show": "쇼", - "movie": "영화", - "video": "동영상", - "year": "올해", - "month": "이번 달", - "week": "이번 주", - "today": "오늘", - "hour": "지난 1시간", - "sort": "정렬기준", - "features": "기능별", - "short": "4분 미만", - "long": "20분 초과", + "search_filters_features_option_hd": "HD", + "search_filters_type_option_show": "쇼", + "search_filters_type_option_movie": "영화", + "search_filters_type_option_video": "동영상", + "search_filters_date_option_year": "올해", + "search_filters_date_option_month": "이번 달", + "search_filters_date_option_week": "이번 주", + "search_filters_date_option_today": "오늘", + "search_filters_date_option_hour": "지난 1시간", + "search_filters_sort_label": "정렬기준", + "search_filters_features_label": "기능별", + "search_filters_duration_option_short": "4분 미만", + "search_filters_duration_option_long": "20분 초과", "footer_documentation": "문서", "footer_source_code": "소스 코드", "footer_original_source_code": "원본 소스 코드", "footer_modfied_source_code": "수정된 소스 코드", - "adminprefs_modified_source_code_url_label": "수정된 소스 코드 저장소의 URL" + "adminprefs_modified_source_code_url_label": "수정된 소스 코드 저장소의 URL", + "search_filters_title": "필터" } diff --git a/locales/lt.json b/locales/lt.json index a5cee472..607b3705 100644 --- a/locales/lt.json +++ b/locales/lt.json @@ -121,7 +121,7 @@ "Subscriptions": "Prenumeratos", "search": "ieškoti", "Log out": "Atsijungti", - "Released under the AGPLv3 on Github.": "Išleista pagal AGPLv3 licenciją Github.", + "Released under the AGPLv3 on Github.": "Išleista pagal AGPLv3 licenciją GitHub.", "Source available here.": "Kodas prieinamas čia.", "View JavaScript license information.": "Žiūrėti JavaScript licencijos informaciją.", "View privacy policy.": "Žiūrėti privatumo politiką.", @@ -141,7 +141,6 @@ "Show less": "Rodyti mažiau", "Watch on YouTube": "Žiaurėti Youtube", "Switch Invidious Instance": "Keisti Invidious šaltinį", - "Broken? Try another Invidious Instance": "Neveikia? Bandyk kitą Invidious šaltinį", "Hide annotations": "Slėpti anotacijas", "Show annotations": "Rodyti anotacijas", "Genre: ": "Žanras: ", @@ -329,39 +328,38 @@ "Videos": "Vaizdo įrašai", "Playlists": "Grojaraiščiai", "Community": "Bendruomenė", - "relevance": "Aktualumas", - "rating": "Reitingas", - "date": "Įkėlimo data", - "views": "Peržiūrų skaičius", - "content_type": "Tipas", - "duration": "Trukmė", - "features": "Funkcijos", - "sort": "Rūšiuoti pagal", - "hour": "Per paskutinę valandą", - "today": "Šiandien", - "week": "Šią savaitę", - "month": "Šį mėnesį", - "year": "Šiais metais", - "video": "Vaizdo įrašas", - "channel": "Kanalas", - "playlist": "Grojaraštis", - "movie": "Filmas", - "show": "Serialas", - "hd": "HD", - "subtitles": "Subtitrai/CC", - "creative_commons": "Creative Commons", - "3d": "3D", - "live": "Tiesiogiai", - "4k": "4K", - "location": "Vietovė", - "hdr": "HDR", - "filter": "Filtras", + "search_filters_sort_option_relevance": "Aktualumas", + "search_filters_sort_option_rating": "Reitingas", + "search_filters_sort_option_date": "Įkėlimo data", + "search_filters_sort_option_views": "Peržiūrų skaičius", + "search_filters_type_label": "Tipas", + "search_filters_duration_label": "Trukmė", + "search_filters_features_label": "Funkcijos", + "search_filters_sort_label": "Rūšiuoti pagal", + "search_filters_date_option_hour": "Per paskutinę valandą", + "search_filters_date_option_today": "Šiandien", + "search_filters_date_option_week": "Šią savaitę", + "search_filters_date_option_month": "Šį mėnesį", + "search_filters_date_option_year": "Šiais metais", + "search_filters_type_option_video": "Vaizdo įrašas", + "search_filters_type_option_channel": "Kanalas", + "search_filters_type_option_playlist": "Grojaraštis", + "search_filters_type_option_movie": "Filmas", + "search_filters_type_option_show": "Serialas", + "search_filters_features_option_hd": "HD", + "search_filters_features_option_subtitles": "Subtitrai/CC", + "search_filters_features_option_c_commons": "Creative Commons", + "search_filters_features_option_three_d": "3D", + "search_filters_features_option_live": "Tiesiogiai", + "search_filters_features_option_four_k": "4K", + "search_filters_features_option_location": "Vietovė", + "search_filters_features_option_hdr": "HDR", "Current version: ": "Dabartinė versija: ", "next_steps_error_message": "Po to turėtumėte pabandyti: ", "next_steps_error_message_refresh": "Atnaujinti", "next_steps_error_message_go_to_youtube": "Eiti į YouTube", - "short": "Trumpas (< 4 minučių)", - "long": "Ilgas (> 20 minučių)", + "search_filters_duration_option_short": "Trumpas (< 4 minučių)", + "search_filters_duration_option_long": "Ilgas (> 20 minučių)", "footer_documentation": "Dokumentacija", "footer_source_code": "Pirminis kodas", "footer_original_source_code": "Pradinis pirminis kodas", @@ -372,5 +370,6 @@ "preferences_quality_dash_label": "Pageidaujama DASH vaizdo kokybė: ", "preferences_quality_dash_option_best": "Geriausia", "preferences_quality_dash_option_worst": "Blogiausia", - "preferences_quality_dash_option_auto": "Automatinis" + "preferences_quality_dash_option_auto": "Automatinis", + "search_filters_title": "Filtras" } diff --git a/locales/nb-NO.json b/locales/nb-NO.json index 1c5ffbc8..8d80c10c 100644 --- a/locales/nb-NO.json +++ b/locales/nb-NO.json @@ -29,7 +29,7 @@ "Export": "Eksporter", "Export subscriptions as OPML": "Eksporter abonnementer som OPML", "Export subscriptions as OPML (for NewPipe & FreeTube)": "Eksporter abonnementer som OPML (for NewPipe og FreeTube)", - "Export data as JSON": "Eksporter data som JSON", + "Export data as JSON": "Eksporter Invidiousdata som JSON", "Delete account?": "Slett konto?", "History": "Historikk", "An alternative front-end to YouTube": "En alternativ grenseflate for YouTube", @@ -66,7 +66,7 @@ "preferences_related_videos_label": "Vis relaterte videoer? ", "preferences_annotations_label": "Vis merknader som forvalg? ", "preferences_extend_desc_label": "Utvid videobeskrivelse automatisk: ", - "preferences_vr_mode_label": "Interaktive 360-gradersfilmer: ", + "preferences_vr_mode_label": "Interaktive 360-gradersfilmer (krever WebGL): ", "preferences_category_visual": "Visuelle innstillinger", "preferences_player_style_label": "Avspillerstil: ", "Dark mode: ": "Mørk drakt: ", @@ -121,7 +121,7 @@ "Subscriptions": "Abonnement", "search": "søk", "Log out": "Logg ut", - "Released under the AGPLv3 on Github.": "Tilgjengelig med AGPLv3-lisens på Github.", + "Released under the AGPLv3 on Github.": "Tilgjengelig med AGPLv3-lisens på GitHub.", "Source available here.": "Kildekode tilgjengelig her.", "View JavaScript license information.": "Vis JavaScript-lisensinfo.", "View privacy policy.": "Vis personvernspraksis.", @@ -141,7 +141,6 @@ "Show less": "Vis mindre", "Watch on YouTube": "Vis video på YouTube", "Switch Invidious Instance": "Bytt Invidious-instans", - "Broken? Try another Invidious Instance": "Knekt? Forsøk en annen Invidious-instans", "Hide annotations": "Skjul merknader", "Show annotations": "Vis merknader", "Genre: ": "Sjanger: ", @@ -199,7 +198,7 @@ "No such user": "Ugyldig bruker", "Token is expired, please try again": "Symbol utløpt, prøv igjen", "English": "Engelsk", - "English (auto-generated)": "Engelsk (auto-generert)", + "English (auto-generated)": "Engelsk (laget automatisk)", "Afrikaans": "Afrikansk", "Albanian": "Albansk", "Amharic": "Amharisk", @@ -329,40 +328,39 @@ "Videos": "Videoer", "Playlists": "Spillelister", "Community": "Gemenskap", - "relevance": "relevans", - "rating": "vurdering", - "date": "dato", - "views": "visninger", - "content_type": "innholdstype", - "duration": "varighet", - "features": "funksjoner", - "sort": "sorter", - "hour": "time", - "today": "i dag", - "week": "uke", - "month": "måned", - "year": "år", - "video": "video", - "channel": "kanal", - "playlist": "spilleliste", - "movie": "film", - "show": "vis", - "hd": "HD", - "subtitles": "undertekster", - "creative_commons": "Creative Commons", - "3d": "3D", - "live": "direkte", - "4k": "4k", - "location": "sted", - "hdr": "HDR", - "filter": "filtrer", + "search_filters_sort_option_relevance": "relevans", + "search_filters_sort_option_rating": "vurdering", + "search_filters_sort_option_date": "dato", + "search_filters_sort_option_views": "visninger", + "search_filters_type_label": "innholdstype", + "search_filters_duration_label": "varighet", + "search_filters_features_label": "funksjoner", + "search_filters_sort_label": "sorter", + "search_filters_date_option_hour": "time", + "search_filters_date_option_today": "i dag", + "search_filters_date_option_week": "uke", + "search_filters_date_option_month": "måned", + "search_filters_date_option_year": "år", + "search_filters_type_option_video": "video", + "search_filters_type_option_channel": "kanal", + "search_filters_type_option_playlist": "spilleliste", + "search_filters_type_option_movie": "film", + "search_filters_type_option_show": "vis", + "search_filters_features_option_hd": "HD", + "search_filters_features_option_subtitles": "undertekster", + "search_filters_features_option_c_commons": "Creative Commons", + "search_filters_features_option_three_d": "3D", + "search_filters_features_option_live": "direkte", + "search_filters_features_option_four_k": "4k", + "search_filters_features_option_location": "sted", + "search_filters_features_option_hdr": "HDR", "Current version: ": "Gjeldende versjon: ", "next_steps_error_message": "Etterpå bør du prøve dette: ", "next_steps_error_message_refresh": "Gjenoppfrisk", "next_steps_error_message_go_to_youtube": "Gå til YouTube", - "long": "Lang (> 20 minutter)", + "search_filters_duration_option_long": "Lang (> 20 minutter)", "footer_donate_page": "Doner", - "short": "Kort (< 4 minutter)", + "search_filters_duration_option_short": "Kort (< 4 minutter)", "footer_documentation": "Dokumentasjon", "footer_source_code": "Kildekode", "footer_original_source_code": "Opprinnelig kildekode", @@ -384,8 +382,8 @@ "preferences_quality_dash_option_240p": "240p", "preferences_quality_dash_option_144p": "144p", "invidious": "Invidious", - "purchased": "Kjøpt", - "360": "360°", + "search_filters_features_option_purchased": "Kjøpt", + "search_filters_features_option_three_sixty": "360°", "none": "intet", "videoinfo_watch_on_youTube": "Se på YouTube", "videoinfo_youTube_embed_link": "Bak inn", @@ -432,10 +430,35 @@ "generic_count_years": "{{count}} år", "generic_count_years_plural": "{{count}} år", "crash_page_read_the_faq": "lest de <a href=\"`x`\">Ofte stilte spørsmålene (OSS/FAQ)</a>", - "crash_page_search_issue": "søkt etter <a href=\"`x`\">eksisterende utfordringer på Github</a>", + "crash_page_search_issue": "søkt etter <a href=\"`x`\">eksisterende utfordringer på GitHub</a>", "crash_page_you_found_a_bug": "Det ser ut til at du fant en feil i Invidious!", "crash_page_refresh": "forsøkt å <a href=\"`x`\">laste siden på nytt</a>", "crash_page_switch_instance": "forsøkt et <a href=\"`x`\">annet eksemplar</a>", "crash_page_before_reporting": "Før du rapporterer en feil, sikre at du har:", - "crash_page_report_issue": "Hvis intet av det overnevnte hjalp, <a href=\"`x`\">lag en ny utfordring på Github</a> (fortrinnsvis på engelsk) og ta med følgende tekstbit i meldingen dit (IKKE oversett denne teksten):" + "crash_page_report_issue": "Sett at det overnevnte ikke hjalp, <a href=\"`x`\">lag en ny utfordring på GitHub</a> (fortrinnsvis på engelsk) og få med følgende tekstbit i meldingen dithen (IKKE oversett denne teksten):", + "English (United Kingdom)": "Engelsk (Storbritannia)", + "English (United States)": "Engelsk (USA)", + "Cantonese (Hong Kong)": "Kantonesisk (Hong Kong)", + "Portuguese (Brazil)": "Portugisisk (Brasil)", + "Spanish (Mexico)": "Spansk (Mexico)", + "Spanish (Spain)": "Spansk (Spania)", + "Spanish (auto-generated)": "Spansk (laget automatisk)", + "Vietnamese (auto-generated)": "Vietnamesisk (laget automatisk)", + "preferences_watch_history_label": "Aktiver seerhistorikk: ", + "Chinese": "Kinesisk", + "Chinese (China)": "Kinesisk (Kina)", + "Chinese (Hong Kong)": "Kinesisk (Hong Kong)", + "Chinese (Taiwan)": "Kinesisk (Taiwan)", + "French (auto-generated)": "Fransk (laget automatisk)", + "German (auto-generated)": "Tysk (laget automatisk)", + "Indonesian (auto-generated)": "Indonesisk (laget automatisk)", + "Interlingue": "Interlingue", + "Italian (auto-generated)": "Italiensk (laget automatisk)", + "Japanese (auto-generated)": "Japansk (laget automatisk)", + "Korean (auto-generated)": "Koreansk (laget automatisk)", + "Portuguese (auto-generated)": "Portugisisk (laget automatisk)", + "Russian (auto-generated)": "Russisk (laget automatisk)", + "Dutch (auto-generated)": "Nederlandsk (laget automatisk)", + "Turkish (auto-generated)": "Tyrkisk (laget automatisk)", + "search_filters_title": "Filtrer" } diff --git a/locales/nl.json b/locales/nl.json index d148d872..7e9ddba6 100644 --- a/locales/nl.json +++ b/locales/nl.json @@ -323,33 +323,32 @@ "Videos": "Video's", "Playlists": "Afspeellijsten", "Community": "Gemeenschap", - "relevance": "relevantie", - "rating": "beoordeling", - "date": "datum", - "views": "keren bekeken", - "content_type": "Type inhoud", - "duration": "duur", - "features": "eigenschappen", - "sort": "sorteren", - "hour": "uur", - "today": "vandaag", - "week": "week", - "month": "maand", - "year": "jaar", - "video": "video", - "channel": "kanaal", - "playlist": "afspeellijst", - "movie": "film", - "show": "show", - "hd": "HD", - "subtitles": "ondertitels", - "creative_commons": "Creative Commons", - "3d": "3D", - "live": "Live", - "4k": "4K", - "location": "locatie", - "hdr": "HDR", - "filter": "verfijnen", + "search_filters_sort_option_relevance": "relevantie", + "search_filters_sort_option_rating": "beoordeling", + "search_filters_sort_option_date": "datum", + "search_filters_sort_option_views": "keren bekeken", + "search_filters_type_label": "Type inhoud", + "search_filters_duration_label": "duur", + "search_filters_features_label": "eigenschappen", + "search_filters_sort_label": "sorteren", + "search_filters_date_option_hour": "uur", + "search_filters_date_option_today": "vandaag", + "search_filters_date_option_week": "week", + "search_filters_date_option_month": "maand", + "search_filters_date_option_year": "jaar", + "search_filters_type_option_video": "video", + "search_filters_type_option_channel": "kanaal", + "search_filters_type_option_playlist": "afspeellijst", + "search_filters_type_option_movie": "film", + "search_filters_type_option_show": "show", + "search_filters_features_option_hd": "HD", + "search_filters_features_option_subtitles": "ondertitels", + "search_filters_features_option_c_commons": "Creative Commons", + "search_filters_features_option_three_d": "3D", + "search_filters_features_option_live": "Live", + "search_filters_features_option_four_k": "4K", + "search_filters_features_option_location": "locatie", + "search_filters_features_option_hdr": "HDR", "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): ", @@ -357,8 +356,8 @@ "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)", + "Released under the AGPLv3 on Github.": "Uitgebracht onder de AGPLv3 op GitHub.", + "search_filters_duration_option_short": "Kort (<4 minuten)", "next_steps_error_message_refresh": "Vernieuwen", "next_steps_error_message_go_to_youtube": "Ga naar YouTube", "footer_donate_page": "Doneren", @@ -366,10 +365,9 @@ "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)", + "search_filters_duration_option_long": "Lang (> 20 minuten)", "preferences_quality_option_dash": "DASH (adaptieve kwaliteit)", "preferences_quality_option_hd720": "HD720", "preferences_quality_option_medium": "Gemiddeld", @@ -397,6 +395,7 @@ "Video unavailable": "Video onbeschikbaar", "preferences_save_player_pos_label": "Huidig afspeeltijdstip opslaan: ", "none": "geen", - "purchased": "Gekocht", - "360": "360º" + "search_filters_features_option_purchased": "Gekocht", + "search_filters_features_option_three_sixty": "360º", + "search_filters_title": "Verfijnen" } diff --git a/locales/pl.json b/locales/pl.json index 0f4e0927..37f951a3 100644 --- a/locales/pl.json +++ b/locales/pl.json @@ -140,7 +140,6 @@ "Show less": "Pokaż mniej", "Watch on YouTube": "Zobacz film na YouTube", "Switch Invidious Instance": "Przełącz instancję Invidious", - "Broken? Try another Invidious Instance": "Nie działa? Spróbuj innej instancji Invidious", "Hide annotations": "Ukryj adnotacje", "Show annotations": "Pokaż adnotacje", "Genre: ": "Gatunek: ", @@ -328,33 +327,32 @@ "Videos": "Filmy", "Playlists": "Playlisty", "Community": "Społeczność", - "relevance": "Trafność", - "rating": "Ocena", - "date": "data", - "views": "Liczba wyświetleń", - "content_type": "Typ", - "duration": "Długość", - "features": "Funkcje", - "sort": "sortuj", - "hour": "godzina", - "today": "dzisiaj", - "week": "tydzień", - "month": "miesiąc", - "year": "rok", - "video": "Film", - "channel": "kanał", - "playlist": "playlista", - "movie": "film", - "show": "pokaż", - "hd": "hd", - "subtitles": "napisy", - "creative_commons": "creative_commons", - "3d": "3d", - "live": "Na żywo", - "4k": "4k", - "location": "Lokalizacja", - "hdr": "hdr", - "filter": "filtr", + "search_filters_sort_option_relevance": "Trafność", + "search_filters_sort_option_rating": "Ocena", + "search_filters_sort_option_date": "data", + "search_filters_sort_option_views": "Liczba wyświetleń", + "search_filters_type_label": "Typ", + "search_filters_duration_label": "Długość", + "search_filters_features_label": "Funkcje", + "search_filters_sort_label": "sortuj", + "search_filters_date_option_hour": "godzina", + "search_filters_date_option_today": "dzisiaj", + "search_filters_date_option_week": "tydzień", + "search_filters_date_option_month": "miesiąc", + "search_filters_date_option_year": "rok", + "search_filters_type_option_video": "Film", + "search_filters_type_option_channel": "kanał", + "search_filters_type_option_playlist": "playlista", + "search_filters_type_option_movie": "film", + "search_filters_type_option_show": "pokaż", + "search_filters_features_option_hd": "hd", + "search_filters_features_option_subtitles": "napisy", + "search_filters_features_option_c_commons": "creative_commons", + "search_filters_features_option_three_d": "3d", + "search_filters_features_option_live": "Na żywo", + "search_filters_features_option_four_k": "4k", + "search_filters_features_option_location": "Lokalizacja", + "search_filters_features_option_hdr": "hdr", "Current version: ": "Aktualna wersja: ", "next_steps_error_message": "Po czym powinien*ś spróbować: ", "next_steps_error_message_refresh": "Odśwież", @@ -432,8 +430,8 @@ "preferences_quality_dash_label": "Preferowana jakość filmu DASH: ", "preferences_quality_dash_option_4320p": "4320p", "preferences_quality_dash_option_2160p": "2160p", - "purchased": "Zakupione", - "360": "360°", + "search_filters_features_option_purchased": "Zakupione", + "search_filters_features_option_three_sixty": "360°", "footer_donate_page": "Dotacja", "none": "żadne", "videoinfo_started_streaming_x_ago": "Transmisja rozpoczęta `x` temu", @@ -446,9 +444,9 @@ "Video unavailable": "Film niedostępny", "preferences_save_player_pos_label": "Zapisz pozycję odtwarzania: ", "preferences_region_label": "Region zawartości: ", - "Released under the AGPLv3 on Github.": "Wydany na licencji AGPLv3 na Github.", - "short": "Krótkie (< 4 minutes)", - "long": "Długie (> 20 minutes)", + "Released under the AGPLv3 on Github.": "Wydany na licencji AGPLv3 na GitHub.", + "search_filters_duration_option_short": "Krótkie (< 4 minutes)", + "search_filters_duration_option_long": "Długie (> 20 minutes)", "footer_documentation": "Dokumentacja", "footer_source_code": "Kod źródłowy", "footer_modfied_source_code": "Zmodyfikowany Kod źródłowy", @@ -476,5 +474,6 @@ "Japanese (auto-generated)": "japoński (wygenerowany automatycznie)", "Russian (auto-generated)": "rosyjski (wygenerowany automatycznie)", "Portuguese (auto-generated)": "portugalski (wygenerowany automatycznie)", - "Portuguese (Brazil)": "portugalski (Brazylia)" + "Portuguese (Brazil)": "portugalski (Brazylia)", + "search_filters_title": "Filtr" } diff --git a/locales/pt-BR.json b/locales/pt-BR.json index 71a232c7..f3a05a1a 100644 --- a/locales/pt-BR.json +++ b/locales/pt-BR.json @@ -123,7 +123,7 @@ "Subscriptions": "Inscrições", "search": "Pesquisar", "Log out": "Sair", - "Released under the AGPLv3 on Github.": "Lançado sob a AGPLv3 no Github.", + "Released under the AGPLv3 on Github.": "Lançado sob a AGPLv3 no GitHub.", "Source available here.": "Código-fonte disponível aqui.", "View JavaScript license information.": "Ver informações da licença do JavaScript.", "View privacy policy.": "Ver a política de privacidade.", @@ -143,7 +143,6 @@ "Show less": "Mostrar menos", "Watch on YouTube": "Assistir no YouTube", "Switch Invidious Instance": "Mudar a instância do Invidious", - "Broken? Try another Invidious Instance": "Quebrou? Tente outra Instância do Invidious", "Hide annotations": "Ocultar anotações", "Show annotations": "Mostrar anotações", "Genre: ": "Gênero: ", @@ -345,41 +344,40 @@ "Videos": "Vídeos", "Playlists": "Listas de reprodução", "Community": "Comunidade", - "relevance": "relevância", - "rating": "avaliação", - "date": "data", - "views": "visualizações", - "content_type": "content_type", - "duration": "duração", - "features": "recursos", - "sort": "ordenar", - "hour": "hora", - "today": "hoje", - "week": "semana", - "month": "mês", - "year": "ano", - "video": "vídeo", - "channel": "Canal", - "playlist": "playlist", - "movie": "filme", - "show": "show", - "hd": "hd", - "subtitles": "legendas", - "creative_commons": "creative_commons", - "3d": "3d", - "live": "ao vivo", - "4k": "4k", - "location": "localização", - "hdr": "hdr", - "filter": "filtro", + "search_filters_sort_option_relevance": "relevância", + "search_filters_sort_option_rating": "avaliação", + "search_filters_sort_option_date": "data", + "search_filters_sort_option_views": "visualizações", + "search_filters_type_label": "content_type", + "search_filters_duration_label": "duração", + "search_filters_features_label": "recursos", + "search_filters_sort_label": "ordenar", + "search_filters_date_option_hour": "hora", + "search_filters_date_option_today": "hoje", + "search_filters_date_option_week": "semana", + "search_filters_date_option_month": "mês", + "search_filters_date_option_year": "ano", + "search_filters_type_option_video": "vídeo", + "search_filters_type_option_channel": "Canal", + "search_filters_type_option_playlist": "playlist", + "search_filters_type_option_movie": "filme", + "search_filters_type_option_show": "show", + "search_filters_features_option_hd": "hd", + "search_filters_features_option_subtitles": "legendas", + "search_filters_features_option_c_commons": "creative_commons", + "search_filters_features_option_three_d": "3d", + "search_filters_features_option_live": "ao vivo", + "search_filters_features_option_four_k": "4k", + "search_filters_features_option_location": "localização", + "search_filters_features_option_hdr": "hdr", "Current version: ": "Versão atual: ", "next_steps_error_message": "Depois disso, você deve tentar: ", "next_steps_error_message_refresh": "Atualizar", "next_steps_error_message_go_to_youtube": "Ir para o YouTube", "footer_donate_page": "Doe", "adminprefs_modified_source_code_url_label": "URL para repositório de código fonte modificado", - "long": "Longo (> 20 minutos)", - "short": "Curto (< 4 minutos)", + "search_filters_duration_option_long": "Longo (> 20 minutos)", + "search_filters_duration_option_short": "Curto (< 4 minutos)", "footer_documentation": "Documentação", "footer_source_code": "Código fonte", "footer_original_source_code": "Código fonte original", @@ -404,10 +402,10 @@ "crash_page_you_found_a_bug": "Parece que você encontrou um erro no Invidious!", "crash_page_before_reporting": "Antes de reportar um erro, verifique se você:", "preferences_save_player_pos_label": "Salvar a posição de reprodução: ", - "purchased": "Comprado", + "search_filters_features_option_purchased": "Comprado", "crash_page_refresh": "tentou <a href=\"`x`\">recarregar a página</a>", "crash_page_switch_instance": "tentou <a href=\"`x`\">usar outra instância</a>", - "crash_page_search_issue": "procurou por um <a href=\"`x`\">erro existente no Github</a>", + "crash_page_search_issue": "procurou por um <a href=\"`x`\">erro existente no GitHub</a>", "crash_page_report_issue": "Se nenhuma opção acima ajudou, por favor <a href=\"`x`\">abra um novo problema no Github</a> (preferencialmente em inglês) e inclua o seguinte texto (NÃO traduza):", "crash_page_read_the_faq": "leu as <a href=\"`x`\">Perguntas Frequentes (FAQ)</a>", "generic_views_count": "{{count}} visualização", @@ -428,7 +426,7 @@ "preferences_quality_dash_option_144p": "144p", "invidious": "Invidious", "preferences_quality_option_medium": "Médio", - "360": "360°", + "search_filters_features_option_three_sixty": "360°", "none": "none", "videoinfo_watch_on_youTube": "Assistir no YouTube", "videoinfo_youTube_embed_link": "Embutir", @@ -437,5 +435,6 @@ "user_created_playlists": "`x` listas de reprodução criadas", "user_saved_playlists": "`x` listas de reprodução salvas", "Video unavailable": "Vídeo indisponível", - "videoinfo_started_streaming_x_ago": "Iniciou a transmissão a `x`" + "videoinfo_started_streaming_x_ago": "Iniciou a transmissão a `x`", + "search_filters_title": "Filtro" } diff --git a/locales/pt-PT.json b/locales/pt-PT.json index 4dba553e..a57a2939 100644 --- a/locales/pt-PT.json +++ b/locales/pt-PT.json @@ -123,7 +123,7 @@ "Subscriptions": "Subscrições", "search": "pesquisar", "Log out": "Terminar sessão", - "Released under the AGPLv3 on Github.": "Lançado sob a AGPLv3 no Github.", + "Released under the AGPLv3 on Github.": "Lançado sob a AGPLv3 no GitHub.", "Source available here.": "Código-fonte disponível aqui.", "View JavaScript license information.": "Ver informações da licença do JavaScript.", "View privacy policy.": "Ver a política de privacidade.", @@ -143,7 +143,6 @@ "Show less": "Mostrar menos", "Watch on YouTube": "Ver no YouTube", "Switch Invidious Instance": "Mudar a instância do Invidious", - "Broken? Try another Invidious Instance": "Falhou? Tente outra Instância do Invidious", "Hide annotations": "Ocultar anotações", "Show annotations": "Mostrar anotações", "Genre: ": "Género: ", @@ -345,35 +344,35 @@ "Videos": "Vídeos", "Playlists": "Listas de reprodução", "Community": "Comunidade", - "relevance": "Relevância", - "rating": "Avaliação", - "date": "Data de envio", - "views": "Visualizações", - "content_type": "Tipo", - "duration": "Duração", - "features": "Funcionalidades", - "sort": "Ordenar por", - "hour": "Última hora", - "today": "Hoje", - "week": "Esta semana", - "month": "Este mês", - "year": "Este ano", - "video": "Vídeo", - "channel": "Canal", - "playlist": "Lista de reprodução", - "movie": "Filme", - "show": "Espetáculo", - "hd": "HD", - "subtitles": "Legendas", - "creative_commons": "Creative Commons", - "3d": "3D", - "live": "Em direto", - "4k": "4K", - "location": "Localização", - "hdr": "HDR", - "filter": "Filtro", + "search_filters_sort_option_relevance": "Relevância", + "search_filters_sort_option_rating": "Avaliação", + "search_filters_sort_option_date": "Data de envio", + "search_filters_sort_option_views": "Visualizações", + "search_filters_type_label": "Tipo", + "search_filters_duration_label": "Duração", + "search_filters_features_label": "Funcionalidades", + "search_filters_sort_label": "Ordenar por", + "search_filters_date_option_hour": "Última hora", + "search_filters_date_option_today": "Hoje", + "search_filters_date_option_week": "Esta semana", + "search_filters_date_option_month": "Este mês", + "search_filters_date_option_year": "Este ano", + "search_filters_type_option_video": "Vídeo", + "search_filters_type_option_channel": "Canal", + "search_filters_type_option_playlist": "Lista de reprodução", + "search_filters_type_option_movie": "Filme", + "search_filters_type_option_show": "Espetáculo", + "search_filters_features_option_hd": "HD", + "search_filters_features_option_subtitles": "Legendas", + "search_filters_features_option_c_commons": "Creative Commons", + "search_filters_features_option_three_d": "3D", + "search_filters_features_option_live": "Em direto", + "search_filters_features_option_four_k": "4K", + "search_filters_features_option_location": "Localização", + "search_filters_features_option_hdr": "HDR", "Current version: ": "Versão atual: ", "next_steps_error_message": "Pode tentar as seguintes opções: ", "next_steps_error_message_refresh": "Atualizar", - "next_steps_error_message_go_to_youtube": "Ir ao YouTube" + "next_steps_error_message_go_to_youtube": "Ir ao YouTube", + "search_filters_title": "Filtro" } diff --git a/locales/pt.json b/locales/pt.json index 0a352f79..df237649 100644 --- a/locales/pt.json +++ b/locales/pt.json @@ -1,14 +1,13 @@ { - "show": "Espetáculo", - "views": "Visualizações", - "date": "Data de envio", - "rating": "Avaliação", - "relevance": "Relevância", - "Broken? Try another Invidious Instance": "Falhou? Tente outra Instância do Invidious", + "search_filters_type_option_show": "Espetáculo", + "search_filters_sort_option_views": "Visualizações", + "search_filters_sort_option_date": "Data de envio", + "search_filters_sort_option_rating": "Avaliação", + "search_filters_sort_option_relevance": "Relevância", "Switch Invidious Instance": "Mudar a instância do Invidious", "Show less": "Mostrar menos", "Show more": "Mostrar mais", - "Released under the AGPLv3 on Github.": "Lançado sob a AGPLv3 no Github.", + "Released under the AGPLv3 on Github.": "Lançado sob a AGPLv3 no GitHub.", "preferences_show_nick_label": "Mostrar nome de utilizador em cima: ", "preferences_automatic_instance_redirect_label": "Redirecionamento de instância automática (solução de último recurso para redirect.invidious.io): ", "preferences_category_misc": "Preferências diversas", @@ -17,28 +16,27 @@ "next_steps_error_message_go_to_youtube": "Ir ao YouTube", "next_steps_error_message": "Pode tentar as seguintes opções: ", "next_steps_error_message_refresh": "Atualizar", - "filter": "Filtro", - "hdr": "HDR", - "location": "Localização", - "4k": "4K", - "live": "Em direto", - "3d": "3D", - "creative_commons": "Creative Commons", - "subtitles": "Legendas", - "hd": "HD", - "movie": "Filme", - "playlist": "Lista de reprodução", - "channel": "Canal", - "video": "Vídeo", - "year": "Este ano", - "month": "Este mês", - "week": "Esta semana", - "today": "Hoje", - "hour": "Última hora", - "sort": "Ordenar por", - "features": "Funcionalidades", - "duration": "Duração", - "content_type": "Tipo", + "search_filters_features_option_hdr": "HDR", + "search_filters_features_option_location": "Localização", + "search_filters_features_option_four_k": "4K", + "search_filters_features_option_live": "Em direto", + "search_filters_features_option_three_d": "3D", + "search_filters_features_option_c_commons": "Creative Commons", + "search_filters_features_option_subtitles": "Legendas", + "search_filters_features_option_hd": "HD", + "search_filters_type_option_movie": "Filme", + "search_filters_type_option_playlist": "Lista de reprodução", + "search_filters_type_option_channel": "Canal", + "search_filters_type_option_video": "Vídeo", + "search_filters_date_option_year": "Este ano", + "search_filters_date_option_month": "Este mês", + "search_filters_date_option_week": "Esta semana", + "search_filters_date_option_today": "Hoje", + "search_filters_date_option_hour": "Última hora", + "search_filters_sort_label": "Ordenar por", + "search_filters_features_label": "Funcionalidades", + "search_filters_duration_label": "Duração", + "search_filters_type_label": "Tipo", "permalink": "hiperligação permanente", "YouTube comment permalink": "Hiperligação permanente do comentário no YouTube", "Download as: ": "Descarregar como: ", @@ -376,8 +374,8 @@ "Unsubscribe": "Anular subscrição", "Shared `x` ago": "Partilhado `x` atrás", "LIVE": "Em direto", - "short": "Curto (< 4 minutos)", - "long": "Longo (> 20 minutos)", + "search_filters_duration_option_short": "Curto (< 4 minutos)", + "search_filters_duration_option_long": "Longo (> 20 minutos)", "footer_source_code": "Código-fonte", "footer_original_source_code": "Código-fonte original", "adminprefs_modified_source_code_url_label": "URL do repositório do código-fonte alterado", @@ -397,8 +395,8 @@ "preferences_quality_dash_option_360p": "360p", "preferences_quality_dash_option_240p": "240p", "preferences_quality_dash_option_144p": "144p", - "purchased": "Comprado", - "360": "360°", + "search_filters_features_option_purchased": "Comprado", + "search_filters_features_option_three_sixty": "360°", "videoinfo_invidious_embed_link": "Incorporar hiperligação", "Video unavailable": "Vídeo não disponível", "invidious": "Invidious", @@ -435,7 +433,8 @@ "crash_page_refresh": "tentou <a href=\"`x`\">recarregar a página</a>", "crash_page_switch_instance": "tentou <a href=\"`x`\">usar outra instância</a>", "crash_page_read_the_faq": "leu as <a href=\"`x`\">Perguntas frequentes (FAQ)</a>", - "crash_page_search_issue": "procurou se <a href=\"`x`\">o erro já foi reportado no Github</a>", + "crash_page_search_issue": "procurou se <a href=\"`x`\">o erro já foi reportado no GitHub</a>", "crash_page_report_issue": "Se nenhuma opção acima ajudou, por favor <a href=\"`x`\">abra um novo problema no Github</a> (preferencialmente em inglês) e inclua o seguinte texto tal qual (NÃO o traduza):", - "user_created_playlists": "`x` listas de reprodução criadas" + "user_created_playlists": "`x` listas de reprodução criadas", + "search_filters_title": "Filtro" } diff --git a/locales/ro.json b/locales/ro.json index 2ea6496b..342f5f37 100644 --- a/locales/ro.json +++ b/locales/ro.json @@ -21,7 +21,7 @@ "No": "Nu", "Import and Export Data": "Importați și Exportați Datele", "Import": "Importați", - "Import Invidious data": "Importați Datele de pe Invidious", + "Import Invidious data": "Importați datele JSON de pe Invidious", "Import YouTube subscriptions": "Importați abonamentele de pe YouTube", "Import FreeTube subscriptions (.db)": "Importați abonamentele de pe FreeTube (.db)", "Import NewPipe subscriptions (.json)": "Importați abonamentele de pe NewPipe (.json)", @@ -29,7 +29,7 @@ "Export": "Exportați", "Export subscriptions as OPML": "Exportați abonamentele în format OPML", "Export subscriptions as OPML (for NewPipe & FreeTube)": "Exportați abonamentele în format OPML (pentru NewPipe și FreeTube)", - "Export data as JSON": "Exportați datele în format JSON", + "Export data as JSON": "Exportați datele Invidious în format JSON", "Delete account?": "Sunteți siguri că doriți să vă ștergeți contul?", "History": "Istoric", "An alternative front-end to YouTube": "O alternativă front-end pentru YouTube", @@ -155,7 +155,7 @@ "Hide replies": "Ascundeți replicile", "Show replies": "Afișați replicile", "Incorrect password": "Parolă incorectă", - "Quota exceeded, try again in a few hours": "Numărul de tentative de conectare a fost depășit. Va rugăm să încercați din nou în câteva ore.", + "Quota exceeded, try again in a few hours": "Numărul de tentative de conectare a fost depășit. Va rugăm să încercați din nou în câteva ore", "Unable to log in, make sure two-factor authentication (Authenticator or SMS) is turned on.": "Conectare eșuată. Dacă nu reușiți să vă conectați, verificați dacă ați activat autentificarea cu doi factori (Autentificator sau SMS).", "Invalid TFA code": "Codul de autentificare cu doi factori este invalid", "Login failed. This may be because two-factor authentication is not turned on for your account.": "Conectare eșuată. Acest lucru ar putea fi cauzat de faptul că nu ați activat autentificarea cu doi factori.", @@ -174,7 +174,7 @@ "Deleted or invalid channel": "Canal șters sau invalid", "This channel does not exist.": "Acest canal nu există.", "Could not get channel info.": "Nu am putut primi informații despre acest canal.", - "Could not fetch comments": "Încărcarea comentariilor a eșuat.", + "Could not fetch comments": "Încărcarea comentariilor a eșuat", "`x` ago": "acum `x`", "Load more": "Vedeți mai mult", "Could not create mix.": "Nu am putut crea această listă de redare.", @@ -187,7 +187,7 @@ "Erroneous challenge": "Challenge invalid", "Erroneous token": "Token invalid", "No such user": "Acest utilizator nu există", - "Token is expired, please try again": "Token-ul este expirat, vă rugăm să reîncercați.", + "Token is expired, please try again": "Jetonul a expirat, vă rugăm să încercați din nou", "English": "Engleză", "English (auto-generated)": "Engleză (generată automat)", "Afrikaans": "Afrikaans", @@ -295,7 +295,7 @@ "Yoruba": "Yoruba", "Zulu": "Zoulou", "Fallback comments: ": "Comentarii alternative: ", - "Popular": "Popular", + "Popular": "Populare", "Top": "Top", "About": "Despre", "Rating: ": "Evaluare: ", @@ -318,5 +318,173 @@ "Videos": "Videoclipuri", "Playlists": "Liste de redare", "Community": "Comunitate", - "Current version: ": "Versiunea actuală: " + "Current version: ": "Versiunea actuală: ", + "crash_page_read_the_faq": "citit lista <a href=\"`x`\">Întrebărilor Frecvente (FAQ)</a>", + "generic_count_days_0": "{{count}} zi", + "generic_count_days_1": "{{count}} zile", + "generic_count_days_2": "{{count}} de zile", + "generic_count_hours_0": "{{count}} oră", + "generic_count_hours_1": "{{count}} ore", + "generic_count_hours_2": "{{count}} de ore", + "generic_count_minutes_0": "{{count}} minut", + "generic_count_minutes_1": "{{count}} minute", + "generic_count_minutes_2": "{{count}} de minute", + "generic_views_count_0": "{{count}} vizionare", + "generic_views_count_1": "{{count}} vizionări", + "generic_views_count_2": "{{count}} de vizionări", + "subscriptions_unseen_notifs_count_0": "{{count}} notificare neverificată", + "subscriptions_unseen_notifs_count_1": "{{count}} notificări neverificate", + "subscriptions_unseen_notifs_count_2": "{{count}} de notificări neverificate", + "crash_page_refresh": "încercat să <a href=\"`x`\">reîmprospătați pagina</a>", + "crash_page_switch_instance": "am încercat să <a href=\"`x`\">folosim o altă instanță</a>", + "preferences_watch_history_label": "Activează istoricul: ", + "invidious": "Invidious", + "preferences_vr_mode_label": "Videoclipuri interactive de 360 de grade (necesită WebGL): ", + "English (United Kingdom)": "Engleză (Regatul Unit)", + "English (United States)": "Engleză (Statele Unite ale Americii)", + "Chinese": "Chineză", + "Chinese (China)": "Chineză (China)", + "Chinese (Hong Kong)": "Chineză (Hong Kong)", + "Chinese (Taiwan)": "Chineză (Taiwan)", + "Cantonese (Hong Kong)": "Cantoneză (Hong Kong)", + "Portuguese (auto-generated)": "Portugheză (generată automat)", + "Portuguese (Brazil)": "Portugheză (Brazilia)", + "Russian (auto-generated)": "Rusă (generată automat)", + "Turkish (auto-generated)": "Turcă (generată automat)", + "Vietnamese (auto-generated)": "Vietnameză (generată automat)", + "videoinfo_started_streaming_x_ago": "În direct de acum `x`", + "preferences_quality_dash_option_2160p": "2160p", + "footer_modfied_source_code": "Codul sursă modificat", + "preferences_quality_dash_label": "Calitatea video DASH preferată: ", + "generic_videos_count_0": "{{count}} videoclip", + "generic_videos_count_1": "{{count}} videoclipuri", + "generic_videos_count_2": "{{count}} de videoclipuri", + "generic_playlists_count_0": "{{count}} playlist", + "generic_playlists_count_1": "{{count}} playlisturi", + "generic_playlists_count_2": "{{count}} de playlisturi", + "tokens_count_0": "{{count}} jeton", + "tokens_count_1": "{{count}} jetoane", + "tokens_count_2": "{{count}} de jetoane", + "comments_points_count_0": "{{count}} punct", + "comments_points_count_1": "{{count}} puncte", + "comments_points_count_2": "{{count}} de puncte", + "Spanish (Spain)": "Spaniolă (Spania)", + "Video unavailable": "Videoclip indisponibil", + "crash_page_search_issue": "căutat <a href=\"`x`\">sugestiile existente pe GitHub</a>", + "Show more": "Afișați mai mult", + "Released under the AGPLv3 on Github.": "Lansat sub licența AGPLv3 pe GitHub.", + "preferences_quality_option_dash": "DASH (calitate adaptativă)", + "preferences_quality_option_hd720": "HD720", + "preferences_quality_option_small": "Mică", + "preferences_quality_dash_option_1440p": "1440p", + "preferences_quality_dash_option_1080p": "1080p", + "preferences_category_misc": "Setări diverse", + "preferences_automatic_instance_redirect_label": "Redirecționare automată de instanță (trecere prin redirect.invidious.io): ", + "preferences_quality_dash_option_480p": "480p", + "preferences_quality_option_medium": "Medie", + "Switch Invidious Instance": "Schimbă instanța Invidious", + "preferences_quality_dash_option_720p": "720p", + "preferences_quality_dash_option_auto": "Automatică", + "preferences_quality_dash_option_best": "Cea mai bună", + "preferences_quality_dash_option_worst": "Cea mai redusă", + "preferences_quality_dash_option_4320p": "4320p", + "preferences_quality_dash_option_360p": "360p", + "preferences_region_label": "Țară de conținut: ", + "preferences_extend_desc_label": "Extindeți automat descrierea: ", + "preferences_show_nick_label": "Afișați numele de utilizator pe partea de sus: ", + "generic_subscribers_count_0": "{{count}} abonat", + "generic_subscribers_count_1": "{{count}} abonați", + "generic_subscribers_count_2": "{{count}} de abonați", + "generic_subscriptions_count_0": "{{count}} abonament", + "generic_subscriptions_count_1": "{{count}} abonamente", + "generic_subscriptions_count_2": "{{count}} de abonamente", + "Search": "Căutați", + "search_filters_title": "Filtre", + "search_filters_date_label": "Data încărcării", + "none": "niciunul", + "search_message_use_another_instance": " Puteți <a href=\"`x`\">căuta într-o altă instanță</a>.", + "comments_view_x_replies_0": "Afișați {{count}} răspuns", + "comments_view_x_replies_1": "Afișați {{count}} răspunsuri", + "comments_view_x_replies_2": "Afișați {{count}} de răspunsuri", + "search_message_no_results": "Nu s-au găsit rezultate.", + "Dutch (auto-generated)": "Olandeză (generată automat)", + "Indonesian (auto-generated)": "Indoneziană (generată automat)", + "German (auto-generated)": "Germană (generată automat)", + "French (auto-generated)": "Franceză (generată automat)", + "Interlingue": "Interlingue", + "Italian (auto-generated)": "Italiană (generată automat)", + "Japanese (auto-generated)": "Japoneză (generată automat)", + "Korean (auto-generated)": "Coreeană (generată automat)", + "Spanish (auto-generated)": "Spaniolă (generată automat)", + "search_filters_date_option_none": "Oricând", + "search_filters_date_option_year": "an", + "search_filters_type_option_channel": "canal", + "Spanish (Mexico)": "Spaniolă (Mexic)", + "generic_count_weeks_0": "{{count}} săptămână", + "generic_count_weeks_1": "{{count}} săptămâni", + "generic_count_weeks_2": "{{count}} de săptămâni", + "generic_count_seconds_0": "{{count}} secundă", + "generic_count_seconds_1": "{{count}} secunde", + "generic_count_seconds_2": "{{count}} de secunde", + "search_filters_type_option_video": "videoclip", + "generic_count_years_0": "{{count}} an", + "generic_count_years_1": "{{count}} ani", + "generic_count_years_2": "{{count}} de ani", + "generic_count_months_0": "{{count}} lună", + "generic_count_months_1": "{{count}} luni", + "generic_count_months_2": "{{count}} de luni", + "search_filters_duration_label": "durată", + "search_filters_date_option_month": "lună", + "search_filters_type_label": "Tip", + "search_filters_date_option_today": "azi", + "search_filters_date_option_week": "săptămână", + "search_filters_features_option_vr180": "VR180", + "search_filters_type_option_playlist": "playlist", + "search_filters_type_option_movie": "film", + "search_filters_type_option_show": "emisiune", + "search_filters_duration_option_short": "Scurt (< 4 minute)", + "search_filters_duration_option_medium": "Medie (4 - 20 de minute)", + "search_filters_duration_option_none": "Fără limită", + "search_filters_duration_option_long": "Lungă (> 20 de minute)", + "search_filters_features_label": "atribute", + "search_filters_features_option_live": "în direct", + "search_filters_features_option_four_k": "4K", + "search_filters_features_option_c_commons": "Creative Commons", + "search_filters_features_option_three_sixty": "360°", + "search_filters_features_option_three_d": "3D", + "search_filters_features_option_subtitles": "subtitrări/CC", + "search_filters_features_option_hd": "HD", + "search_filters_features_option_hdr": "HDR", + "search_filters_features_option_purchased": "Cumpărate", + "next_steps_error_message": "După ce ar trebui să încercați să: ", + "user_saved_playlists": "`x` playlisturi salvate", + "search_filters_features_option_location": "locație", + "search_filters_sort_label": "Sortați după", + "search_filters_sort_option_relevance": "relevanță", + "search_filters_sort_option_rating": "clasificare", + "search_filters_sort_option_date": "Data încărcării", + "search_filters_sort_option_views": "Numărul de vizionări", + "footer_source_code": "Codul sursă", + "search_filters_apply_button": "Aplicați filtrele selectate", + "footer_original_source_code": "Codul sursă original", + "next_steps_error_message_refresh": "Reîmprospătează", + "next_steps_error_message_go_to_youtube": "Mergeți pe YouTube", + "footer_donate_page": "Donați", + "adminprefs_modified_source_code_url_label": "URL către depozitul de cod sursă modificat", + "footer_documentation": "Documentație", + "videoinfo_youTube_embed_link": "Încorporați", + "videoinfo_watch_on_youTube": "Vizionați pe YouTube", + "videoinfo_invidious_embed_link": "Link de încorporare", + "download_subtitles": "Subtitrări - `x` (.vtt)", + "user_created_playlists": "`x` playlisturi create", + "preferences_save_player_pos_label": "Salvați poziția de redare: ", + "crash_page_you_found_a_bug": "Se pare că ați găsit un bug în aplicația Invidious!", + "crash_page_before_reporting": "Înainte de a reporta bugul, asigurați-vă că ați:", + "search_filters_date_option_hour": "oră", + "search_message_change_filters_or_query": "Încercați să lărgiți căutarea sau să modificați filtrele.", + "crash_page_report_issue": "Dacă niciuna dintre sugestiile de mai sus v-a ajutat, vă rugăm să <a href=\"`x`\">postați o nouă sugestie pe GitHub</a> (cel mai bine în engleză), și să includeți următorul text în post (să nu îl traduceți):", + "search_filters_type_option_all": "orice tip", + "preferences_quality_dash_option_240p": "240p", + "preferences_quality_dash_option_144p": "144p", + "Show less": "Afișați mai puțin" } diff --git a/locales/ru.json b/locales/ru.json index c223bcf8..88bb64ad 100644 --- a/locales/ru.json +++ b/locales/ru.json @@ -121,7 +121,7 @@ "Subscriptions": "Подписки", "search": "поиск", "Log out": "Выйти", - "Released under the AGPLv3 on Github.": "Выпущено под лицензией AGPLv3 на Github.", + "Released under the AGPLv3 on Github.": "Выпущено под лицензией AGPLv3 на GitHub.", "Source available here.": "Исходный код доступен здесь.", "View JavaScript license information.": "Посмотреть информацию по лицензии JavaScript.", "View privacy policy.": "Посмотреть политику конфиденциальности.", @@ -141,7 +141,6 @@ "Show less": "Показать меньше", "Watch on YouTube": "Смотреть на YouTube", "Switch Invidious Instance": "Сменить экземпляр Invidious", - "Broken? Try another Invidious Instance": "Сломался? Попробуйте другой экземпляр Invidious", "Hide annotations": "Скрыть аннотации", "Show annotations": "Показать аннотации", "Genre: ": "Жанр: ", @@ -329,39 +328,38 @@ "Videos": "Видео", "Playlists": "Плейлисты", "Community": "Сообщество", - "relevance": "Актуальность", - "rating": "Рейтинг", - "date": "Дата загрузки", - "views": "Просмотры", - "content_type": "Тип", - "duration": "Длительность", - "features": "Функции", - "sort": "Сортировать по", - "hour": "Последний час", - "today": "Сегодня", - "week": "Эта неделя", - "month": "Этот месяц", - "year": "Этот год", - "video": "Видео", - "channel": "Канал", - "playlist": "Плейлист", - "movie": "Фильм", - "show": "Показать", - "hd": "HD", - "subtitles": "Субтитры", - "creative_commons": "Creative Commons", - "3d": "3D", - "live": "Прямой эфир", - "4k": "4K", - "location": "Местоположение", - "hdr": "HDR", - "filter": "Фильтр", + "search_filters_sort_option_relevance": "Актуальность", + "search_filters_sort_option_rating": "Рейтинг", + "search_filters_sort_option_date": "Дата загрузки", + "search_filters_sort_option_views": "Просмотры", + "search_filters_type_label": "Тип", + "search_filters_duration_label": "Длительность", + "search_filters_features_label": "Функции", + "search_filters_sort_label": "Сортировать по", + "search_filters_date_option_hour": "Последний час", + "search_filters_date_option_today": "Сегодня", + "search_filters_date_option_week": "Эта неделя", + "search_filters_date_option_month": "Этот месяц", + "search_filters_date_option_year": "Этот год", + "search_filters_type_option_video": "Видео", + "search_filters_type_option_channel": "Канал", + "search_filters_type_option_playlist": "Плейлист", + "search_filters_type_option_movie": "Фильм", + "search_filters_type_option_show": "Показать", + "search_filters_features_option_hd": "HD", + "search_filters_features_option_subtitles": "Субтитры", + "search_filters_features_option_c_commons": "Creative Commons", + "search_filters_features_option_three_d": "3D", + "search_filters_features_option_live": "Прямой эфир", + "search_filters_features_option_four_k": "4K", + "search_filters_features_option_location": "Местоположение", + "search_filters_features_option_hdr": "HDR", "Current version: ": "Текущая версия: ", "next_steps_error_message": "После чего следует попробовать: ", "next_steps_error_message_refresh": "Обновить", "next_steps_error_message_go_to_youtube": "Перейти на YouTube", - "short": "Короткие (< 4 минут)", - "long": "Длинные (> 20 минут)", + "search_filters_duration_option_short": "Короткие (< 4 минут)", + "search_filters_duration_option_long": "Длинные (> 20 минут)", "preferences_quality_dash_option_best": "Наилучшее", "generic_count_weeks_0": "{{count}} неделя", "generic_count_weeks_1": "{{count}} недели", @@ -437,7 +435,7 @@ "generic_count_seconds_0": "{{count}} секунда", "generic_count_seconds_1": "{{count}} секунды", "generic_count_seconds_2": "{{count}} секунд", - "purchased": "Приобретено", + "search_filters_features_option_purchased": "Приобретено", "videoinfo_started_streaming_x_ago": "Трансляция началась `x` назад", "crash_page_switch_instance": "пробовали <a href=\"`x`\">использовать другое зеркало</a>", "crash_page_read_the_faq": "прочли <a href=\"`x`\">Частые Вопросы (ЧаВо)</a>", @@ -457,7 +455,7 @@ "footer_original_source_code": "Оригинальный исходный код", "footer_modfied_source_code": "Изменённый исходный код", "user_saved_playlists": "`x` сохранённых плейлистов", - "crash_page_search_issue": "искали <a href=\"`x`\">похожую проблему на Github</a>", + "crash_page_search_issue": "искали <a href=\"`x`\">похожую проблему на GitHub</a>", "comments_points_count_0": "{{count}} плюс", "comments_points_count_1": "{{count}} плюса", "comments_points_count_2": "{{count}} плюсов", @@ -473,9 +471,10 @@ "preferences_quality_dash_option_240p": "240p", "preferences_quality_dash_option_144p": "144p", "invidious": "Invidious", - "360": "360°", + "search_filters_features_option_three_sixty": "360°", "Video unavailable": "Видео недоступно", "preferences_save_player_pos_label": "Запоминать позицию: ", "preferences_region_label": "Страна: ", - "preferences_watch_history_label": "Включить историю просмотров " + "preferences_watch_history_label": "Включить историю просмотров ", + "search_filters_title": "Фильтр" } diff --git a/locales/sk.json b/locales/sk.json index f20ad75a..cdb3a596 100644 --- a/locales/sk.json +++ b/locales/sk.json @@ -18,15 +18,15 @@ "No": "Nie", "Import and Export Data": "Import a Export údajov", "Import": "Import", - "Import Invidious data": "Importovať údaje Invidious", - "Import YouTube subscriptions": "Importovať odbery YouTube", + "Import Invidious data": "Importovať JSON údaje Invidious", + "Import YouTube subscriptions": "Importovať odbery YouTube/OPML", "Import FreeTube subscriptions (.db)": "Importovať odbery FreeTube (.db)", "Import NewPipe subscriptions (.json)": "Importovať odbery NewPipe (.json)", "Import NewPipe data (.zip)": "Importovať údaje NewPipe (.zip)", "Export": "Export", "Export subscriptions as OPML": "Exportovať odbery ako OPML", "Export subscriptions as OPML (for NewPipe & FreeTube)": "Exportovať odbery ako OPML (pre NewPipe a FreeTube)", - "Export data as JSON": "Export údajov ako JSON", + "Export data as JSON": "Exportovať údaje Invidious ako JSON", "Delete account?": "Zrušiť účet?", "History": "História", "An alternative front-end to YouTube": "Alternatívny front-end pre YouTube", @@ -84,5 +84,23 @@ "preferences_unseen_only_label": "Zobraziť iba neprehrané: ", "preferences_notifications_only_label": "Zobraziť iba upozornenia (ak existujú): ", "Enable web notifications": "Povoliť webové upozornenia", - "`x` uploaded a video": "`x` nahral(a) video" + "`x` uploaded a video": "`x` nahral(a) video", + "generic_views_count_0": "{{count}} zhliadnutie", + "generic_views_count_1": "{{count}} zhliadnutia", + "generic_views_count_2": "{{count}} zhliadnutí", + "generic_subscribers_count_0": "{{count}} odberateľ", + "generic_subscribers_count_1": "{{count}} odberatelia", + "generic_subscribers_count_2": "{{count}} odberateľov", + "Shared `x` ago": "Zverejnené pred `x`", + "generic_playlists_count_0": "{{count}} playlist", + "generic_playlists_count_1": "{{count}} playlisty", + "generic_playlists_count_2": "{{count}} playlistov", + "generic_videos_count_0": "{{count}} video", + "generic_videos_count_1": "{{count}} videá", + "generic_videos_count_2": "{{count}} videí", + "generic_subscriptions_count_0": "{{count}} odber", + "generic_subscriptions_count_1": "{{count}} odbery", + "generic_subscriptions_count_2": "{{count}} odberov", + "Authorize token for `x`?": "Autorizovať token pre `x`?", + "View playlist on YouTube": "Zobraziť playlist na YouTube" } diff --git a/locales/sq.json b/locales/sq.json index 3e2a3fb1..76f1eaa3 100644 --- a/locales/sq.json +++ b/locales/sq.json @@ -26,11 +26,11 @@ "Tamil": "Tamilisht", "Telugu": "Telugu", "Vietnamese": "Vietnamisht", - "creative_commons": "Creative Commons", - "3d": "3D", - "live": "Drejtpërsëdrejti", - "4k": "4K", - "location": "Vendndodhja", + "search_filters_features_option_c_commons": "Creative Commons", + "search_filters_features_option_three_d": "3D", + "search_filters_features_option_live": "Drejtpërsëdrejti", + "search_filters_features_option_four_k": "4K", + "search_filters_features_option_location": "Vendndodhja", "videoinfo_watch_on_youTube": "Shiheni në YouTube", "videoinfo_youTube_embed_link": "Trupëzojeni", "videoinfo_invidious_embed_link": "Lidhje Trupëzimi", @@ -127,7 +127,7 @@ "Subscriptions": "Pajtime", "search": "kërko", "Log out": "Dilni", - "Released under the AGPLv3 on Github.": "Hedhur në qarkullim në Github sipas licencës AGPLv3.", + "Released under the AGPLv3 on Github.": "Hedhur në qarkullim në GitHub sipas licencës AGPLv3.", "Source available here.": "Burimi i passhëm që këtu.", "View JavaScript license information.": "Shihni hollësi licence JavaScript.", "View privacy policy.": "Shihni rregulla privatësie.", @@ -147,7 +147,6 @@ "Show less": "Shfaq më pak", "Watch on YouTube": "Shiheni në YouTube", "Switch Invidious Instance": "Ndërroni Instancë Invidious", - "Broken? Try another Invidious Instance": "E prishur? Provoni një tjetër Instancë Invidious", "Hide annotations": "Fshihi shënimet", "Show annotations": "Shfaq shënime", "License: ": "Licencë: ", @@ -261,32 +260,32 @@ "Audio mode": "Mënyrë për audion", "Playlists": "Luajlista", "Community": "Bashkësi", - "relevance": "Rëndësi", + "search_filters_sort_option_relevance": "Rëndësi", "Video mode": "Mënyrë video", "Videos": "Video", - "rating": "Vlerësim", - "date": "Datë ngarkimi", - "views": "Numër parjesh", - "content_type": "Lloj", - "duration": "Kohëzgjatje", - "features": "Veçori", - "sort": "Renditi Sipas", - "hour": "Orën e Fundit", - "today": "Sot", - "long": "E gjatë (> 20 minuta)", - "hd": "HD", - "subtitles": "Titra/CC", - "hdr": "HDR", - "week": "Këtë javë", - "month": "Këtë muaj", - "year": "Këtë vit", - "video": "Video", - "channel": "Kanal", - "playlist": "Luajlistë", - "movie": "Film", - "show": "Shfaqe", - "short": "E shkurtër (< 4 minuta)", - "purchased": "Të blera", + "search_filters_sort_option_rating": "Vlerësim", + "search_filters_sort_option_date": "Datë ngarkimi", + "search_filters_sort_option_views": "Numër parjesh", + "search_filters_type_label": "Lloj", + "search_filters_duration_label": "Kohëzgjatje", + "search_filters_features_label": "Veçori", + "search_filters_sort_label": "Renditi Sipas", + "search_filters_date_option_hour": "Orën e Fundit", + "search_filters_date_option_today": "Sot", + "search_filters_duration_option_long": "E gjatë (> 20 minuta)", + "search_filters_features_option_hd": "HD", + "search_filters_features_option_subtitles": "Titra/CC", + "search_filters_features_option_hdr": "HDR", + "search_filters_date_option_week": "Këtë javë", + "search_filters_date_option_month": "Këtë muaj", + "search_filters_date_option_year": "Këtë vit", + "search_filters_type_option_video": "Video", + "search_filters_type_option_channel": "Kanal", + "search_filters_type_option_playlist": "Luajlistë", + "search_filters_type_option_movie": "Film", + "search_filters_type_option_show": "Shfaqe", + "search_filters_duration_option_short": "E shkurtër (< 4 minuta)", + "search_filters_features_option_purchased": "Të blera", "footer_modfied_source_code": "Kod Burim i ndryshuar", "adminprefs_modified_source_code_url_label": "URL e depos së ndryshuar të kodit burim", "none": "asnjë", @@ -370,8 +369,7 @@ "Mongolian": "Mongolisht", "Nepali": "Nepaleze", "Norwegian Bokmål": "Norvegjishte Bokmål", - "360": "360°", - "filter": "Filtroji", + "search_filters_features_option_three_sixty": "360°", "Current version: ": "Versioni i tanishëm: ", "next_steps_error_message": "Pas të cilës duhet të provoni të: ", "next_steps_error_message_refresh": "Rifreskoje", @@ -437,7 +435,7 @@ "Spanish (Spain)": "Spanjisht (Spanjë)", "Turkish (auto-generated)": "Turqisht (të prodhuara automatikisht)", "Vietnamese (auto-generated)": "Vietnamisht (të prodhuara automatikisht)", - "crash_page_search_issue": "kërkuar për <a href=\"`x`\">çështje ekzistuese në Github</a>", + "crash_page_search_issue": "kërkuar për <a href=\"`x`\">çështje ekzistuese në GitHub</a>", "crash_page_report_issue": "Nëse asnjë nga sa më sipër s’ndihmoi, ju lutemi, <a href=\"`x`\">hapni një çështje në GitHub</a> (mundësisht në anglisht) dhe përfshini në mesazhin tuaj tekstin vijues (MOS e përktheni këtë tekst):", "generic_subscriptions_count": "{{count}} pajtim", "generic_subscriptions_count_plural": "{{count}} pajtime", @@ -448,5 +446,6 @@ "Import YouTube subscriptions": "Importoni pajtime YouTube/OPML", "Export data as JSON": "Eksportoji të dhënat Invidious si JSON", "preferences_vr_mode_label": "Video me ndërveprim 360 gradë (lyp WebGL): ", - "Shared `x`": "Ndau me të tjerë `x`" + "Shared `x`": "Ndau me të tjerë `x`", + "search_filters_title": "Filtra" } diff --git a/locales/sr.json b/locales/sr.json index 40e53231..d2f990ae 100644 --- a/locales/sr.json +++ b/locales/sr.json @@ -131,26 +131,25 @@ "YouTube comment permalink": "YouTube komentar trajna veza", "Audio mode": "Audio mod", "Playlists": "Plej liste", - "relevance": "Relevantnost", - "rating": "Ocene", - "date": "Datum otpremanja", - "views": "Broj pregleda", + "search_filters_sort_option_relevance": "Relevantnost", + "search_filters_sort_option_rating": "Ocene", + "search_filters_sort_option_date": "Datum otpremanja", + "search_filters_sort_option_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 (Licenca)", - "3d": "3D", - "hdr": "Video Visoke Rezolucije", - "filter": "Filter", + "search_filters_duration_label": "Trajanje", + "search_filters_features_label": "Karakteristike", + "search_filters_date_option_hour": "Poslednji sat", + "search_filters_date_option_week": "Ove sedmice", + "search_filters_date_option_month": "Ovaj mesec", + "search_filters_date_option_year": "Ove godine", + "search_filters_type_option_video": "Video", + "search_filters_type_option_playlist": "Plej lista", + "search_filters_type_option_movie": "Film", + "search_filters_duration_option_long": "Dugo (> 20 minuta)", + "search_filters_features_option_hd": "HD", + "search_filters_features_option_c_commons": "Creative Commons (Licenca)", + "search_filters_features_option_three_d": "3D", + "search_filters_features_option_hdr": "Video Visoke Rezolucije", "next_steps_error_message": "Nakon čega bi trebali probati: ", "next_steps_error_message_go_to_youtube": "Idi na YouTube", "footer_documentation": "Dokumentacija", @@ -225,13 +224,12 @@ "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", + "search_filters_type_label": "Tip", "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", + "search_filters_type_option_channel": "Kanal", "Hungarian": "Mađarski", "Maori": "Maori (Jezik)", "Manage subscriptions": "Upravljaj zapisima", @@ -243,7 +241,7 @@ "preferences_default_home_label": "Podrazumevana početna stranica: ", "Serbian": "Srpski", "License: ": "Licenca: ", - "live": "Uživo", + "search_filters_features_option_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", @@ -266,14 +264,14 @@ "alphabetically": "po alfabetu", "No such user": "Nepostojeći korisnik", "Subscriptions": "Praćenja", - "today": "Danas", + "search_filters_date_option_today": "Danas", "Finnish": "Finski", "Lao": "Laoski", "Login enabled: ": "Prijava omogućena: ", "Shona": "Šona", - "location": "Lokacija", + "search_filters_features_option_location": "Lokacija", "Load more": "Učitaj više", - "Released under the AGPLv3 on Github.": "Izbačeno pod licencom AGPLv3 na Github-u.", + "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)", @@ -292,7 +290,7 @@ "Czech": "Češki", "Latin": "Latinski", "Videos": "Video klipovi", - "4k": "4К", + "search_filters_features_option_four_k": "4К", "footer_donate_page": "Doniraj", "English": "Engleski", "Arabic": "Arapski", @@ -310,7 +308,7 @@ "Swahili": "Svahili", "Yiddish": "Jidiš", "Zulu": "Zulu", - "subtitles": "Titl/Prevod", + "search_filters_features_option_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", @@ -329,9 +327,9 @@ "Clear watch history": "Obriši istoriju gledanja", "preferences_category_admin": "Administratorska podešavanja", "published": "objavljeno", - "sort": "Poredaj prema", - "show": "Emisija", - "short": "Kratko (< 4 minute)", + "search_filters_sort_label": "Poredaj prema", + "search_filters_type_option_show": "Emisija", + "search_filters_duration_option_short": "Kratko (< 4 minute)", "Current version: ": "Trenutna verzija: ", "Top enabled: ": "Vrh omogućen: ", "Public": "Javno", @@ -369,5 +367,6 @@ "unsubscribe": "prekini sa praćenjem", "Blacklisted regions: ": "Zabranjene oblasti: ", "Polish": "Poljski", - "Yoruba": "Joruba" + "Yoruba": "Joruba", + "search_filters_title": "Filter" } diff --git a/locales/sr_Cyrl.json b/locales/sr_Cyrl.json index 40c50674..c0f1224f 100644 --- a/locales/sr_Cyrl.json +++ b/locales/sr_Cyrl.json @@ -182,14 +182,13 @@ "Georgian": "Грузијски", "Greek": "Грчки", "Hausa": "Хауса", - "video": "Видео", - "playlist": "Плеј листа", - "movie": "Филм", - "long": "Дуго (> 20 минута)", - "creative_commons": "Creative Commons (Лиценца)", - "live": "Уживо", - "location": "Локација", - "filter": "Филтер", + "search_filters_type_option_video": "Видео", + "search_filters_type_option_playlist": "Плеј листа", + "search_filters_type_option_movie": "Филм", + "search_filters_duration_option_long": "Дуго (> 20 минута)", + "search_filters_features_option_c_commons": "Creative Commons (Лиценца)", + "search_filters_features_option_live": "Уживо", + "search_filters_features_option_location": "Локација", "next_steps_error_message": "Након чега би требали пробати: ", "footer_donate_page": "Донирај", "footer_documentation": "Документација", @@ -247,9 +246,9 @@ "`x` marked it with a ❤": "`x` је означио/ла ово са ❤", "Audio mode": "Аудио мод", "Videos": "Видео клипови", - "views": "Број прегледа", - "features": "Карактеристике", - "today": "Данас", + "search_filters_sort_option_views": "Број прегледа", + "search_filters_features_label": "Карактеристике", + "search_filters_date_option_today": "Данас", "%A %B %-d, %Y": "%A %B %-d, %Y", "preferences_locale_label": "Језик: ", "Persian": "Перзијски", @@ -257,7 +256,7 @@ "": "Прикажи `x` коментара", "([^.,0-9]|^)1([^.,0-9]|$)": "Прикажи `x` коментар" }, - "channel": "Канал", + "search_filters_type_option_channel": "Канал", "Haitian Creole": "Хаићански Креолски", "Armenian": "Јерменски", "next_steps_error_message_go_to_youtube": "Иди на YouTube", @@ -265,10 +264,10 @@ "preferences_vr_mode_label": "Интерактивни видео клипови у 360 степени: ", "Switch Invidious Instance": "Промени Invidious инстанцу", "Portuguese": "Португалски", - "week": "Ове седмице", - "show": "Емисија", + "search_filters_date_option_week": "Ове седмице", + "search_filters_type_option_show": "Емисија", "Fallback comments: ": "Коментари у случају отказивања: ", - "hdr": "Видео Високе Резолуције", + "search_filters_features_option_hdr": "Видео Високе Резолуције", "About": "О програму", "Kazakh": "Казашки", "Shared `x`": "Подељено `x`", @@ -277,7 +276,7 @@ "Erroneous challenge": "Погрешан изазов", "Danish": "Дански", "Could not get channel info.": "Узимање података о каналу није успело.", - "hd": "HD", + "search_filters_features_option_hd": "HD", "Slovenian": "Словеначки", "Load more": "Учитај више", "German": "Немачки", @@ -288,12 +287,12 @@ "Southern Sotho": "Јужни Сото", "Popular": "Популарно", "Gujarati": "Гуџарати", - "year": "Ове године", + "search_filters_date_option_year": "Ове године", "Irish": "Ирски", "YouTube comment permalink": "YouTube коментар трајна веза", "Malagasy": "Малгашки", "Token is expired, please try again": "Жетон је истекао, молимо вас да покушате поново", - "short": "Кратко (< 4 минуте)", + "search_filters_duration_option_short": "Кратко (< 4 минуте)", "Samoan": "Самоански", "Tamil": "Тамилски", "Ukrainian": "Украјински", @@ -307,22 +306,22 @@ "Lithuanian": "Литвански", "Icelandic": "Исландски", "Thai": "Тајски", - "month": "Овај месец", - "content_type": "Тип", - "hour": "Последњи сат", + "search_filters_date_option_month": "Овај месец", + "search_filters_type_label": "Тип", + "search_filters_date_option_hour": "Последњи сат", "Spanish": "Шпански", - "date": "Датум отпремања", + "search_filters_sort_option_date": "Датум отпремања", "View as playlist": "Погледај као плеј листу", - "relevance": "Релевантност", + "search_filters_sort_option_relevance": "Релевантност", "Estonian": "Естонски", "Sinhala": "Синхалешки", "Corsican": "Корзикански", "Filipino": "Филипино", "Gaming": "Игрице", "Movies": "Филмови", - "rating": "Оцене", + "search_filters_sort_option_rating": "Оцене", "Top enabled: ": "Врх омогућен: ", - "Released under the AGPLv3 on Github.": "Избачено под лиценцом AGPLv3 на Github-у.", + "Released under the AGPLv3 on Github.": "Избачено под лиценцом AGPLv3 на GitHub-у.", "Afrikaans": "Африканс", "preferences_automatic_instance_redirect_label": "Аутоматско пребацивање на другу инстанцу у случају отказивања (пречи ће назад на редирецт.инвидиоус.ио): ", "Invalid TFA code": "Неважећа TFA кода", @@ -340,12 +339,11 @@ "Swedish": "Шведски", "Music": "Музика", "Download as: ": "Преузми као: ", - "duration": "Трајање", - "sort": "Поредај према", - "subtitles": "Титл/Превод", + "search_filters_duration_label": "Трајање", + "search_filters_sort_label": "Поредај према", + "search_filters_features_option_subtitles": "Титл/Превод", "preferences_extend_desc_label": "Аутоматски прикажи цео опис видеа: ", "Show less": "Прикажи мање", - "Broken? Try another Invidious Instance": "Не функционише исправно? Пробајте другу Invidious инстанцу", "Family friendly? ": "Погодно за породицу? ", "Premieres `x`": "Премерe у `x`", "Bosnian": "Босански", @@ -359,8 +357,8 @@ "Top": "Врх", "Video mode": "Видео мод", "footer_source_code": "Изворна Кода", - "3d": "3D", - "4k": "4K", + "search_filters_features_option_three_d": "3D", + "search_filters_features_option_four_k": "4K", "Erroneous CAPTCHA": "Погрешна CAPTCHA", "`x` ago": "пре `x`", "Arabic": "Арапски", @@ -369,5 +367,6 @@ "Hebrew": "Хебрејски", "Korean": "Корејски", "Kurdish": "Курдски", - "Malay": "Малајски" + "Malay": "Малајски", + "search_filters_title": "Филтер" } diff --git a/locales/sv-SE.json b/locales/sv-SE.json index ab0d0773..777899d0 100644 --- a/locales/sv-SE.json +++ b/locales/sv-SE.json @@ -139,7 +139,6 @@ "Show less": "Visa mindre", "Watch on YouTube": "Titta på YouTube", "Switch Invidious Instance": "Byt Invidious Instans", - "Broken? Try another Invidious Instance": "Trasig? Prova en annan Invidious Instance", "Hide annotations": "Dölj länkar-i-video", "Show annotations": "Visa länkar-i-video", "Genre: ": "Genre: ", @@ -327,39 +326,39 @@ "Videos": "Videor", "Playlists": "Spellistor", "Community": "Gemenskap", - "relevance": "Relevans", - "rating": "Rankning", - "date": "datum", - "views": "visningar", - "content_type": "Typ", - "duration": "Varaktighet", - "features": "Funktioner", - "sort": "Sortera efter", - "hour": "timme", - "today": "idag", - "week": "vecka", - "month": "månad", - "year": "år", - "video": "video", - "channel": "kanal", - "playlist": "spellista", - "movie": "film", - "show": "tv-serie", - "hd": "hd", - "subtitles": "undertexter", - "creative_commons": "creative_commons", - "3d": "3d", - "live": "live", - "4k": "4k", - "location": "plats", - "hdr": "hdr", - "filter": "Filter", + "search_filters_sort_option_relevance": "Relevans", + "search_filters_sort_option_rating": "Rankning", + "search_filters_sort_option_date": "Datum", + "search_filters_sort_option_views": "Visningar", + "search_filters_type_label": "Typ", + "search_filters_duration_label": "Varaktighet", + "search_filters_features_label": "Funktioner", + "search_filters_sort_label": "Sortera efter", + "search_filters_date_option_hour": "timme", + "search_filters_date_option_today": "idag", + "search_filters_date_option_week": "vecka", + "search_filters_date_option_month": "månad", + "search_filters_date_option_year": "år", + "search_filters_type_option_video": "video", + "search_filters_type_option_channel": "kanal", + "search_filters_type_option_playlist": "spellista", + "search_filters_type_option_movie": "film", + "search_filters_type_option_show": "tv-serie", + "search_filters_features_option_hd": "hd", + "search_filters_features_option_subtitles": "undertexter", + "search_filters_features_option_c_commons": "creative_commons", + "search_filters_features_option_three_d": "3d", + "search_filters_features_option_live": "live", + "search_filters_features_option_four_k": "4k", + "search_filters_features_option_location": "plats", + "search_filters_features_option_hdr": "hdr", "Current version: ": "Nuvarande version: ", "next_steps_error_message_refresh": "Uppdatera", "next_steps_error_message_go_to_youtube": "Gå till Youtube", - "Released under the AGPLv3 on Github.": "Publicerad under AGPLv3 på Github.", + "Released under the AGPLv3 on Github.": "Publicerad under AGPLv3 på GitHub.", "footer_source_code": "Källkod", - "long": "Lång (> 20 minuter)", + "search_filters_duration_option_long": "Lång (> 20 minuter)", "footer_documentation": "Dokumentation", - "short": "Kort (< 4 minuter)" + "search_filters_duration_option_short": "Kort (< 4 minuter)", + "search_filters_title": "Filter" } diff --git a/locales/tr.json b/locales/tr.json index 094728fa..b1991c35 100644 --- a/locales/tr.json +++ b/locales/tr.json @@ -121,7 +121,7 @@ "Subscriptions": "Abonelikler", "search": "ara", "Log out": "Çıkış yap", - "Released under the AGPLv3 on Github.": "Github'da AGPLv3 altında yayınlandı.", + "Released under the AGPLv3 on Github.": "GitHub'da AGPLv3 altında yayınlandı.", "Source available here.": "Kaynak kodları burada bulunabilir.", "View JavaScript license information.": "JavaScript lisans bilgilerini görüntüle.", "View privacy policy.": "Gizlilik politikasını görüntüle.", @@ -141,7 +141,6 @@ "Show less": "Daha az göster", "Watch on YouTube": "YouTube'da izle", "Switch Invidious Instance": "Invidious Örneğini Değiştir", - "Broken? Try another Invidious Instance": "Bozuk mu? Başka bir Invidious örneğini deneyin", "Hide annotations": "Ek açıklamaları gizle", "Show annotations": "Ek açıklamaları göster", "Genre: ": "Tür: ", @@ -329,39 +328,38 @@ "Videos": "Videolar", "Playlists": "Oynatma listeleri", "Community": "Topluluk", - "relevance": "İlgi", - "rating": "Değerlendirme", - "date": "Yükleme tarihi", - "views": "Görüntüleme sayısı", - "content_type": "Tür", - "duration": "Süre", - "features": "Özellikler", - "sort": "Sıralama Ölçütü", - "hour": "Son Saat", - "today": "Bugün", - "week": "Bu hafta", - "month": "Bu ay", - "year": "Bu yıl", - "video": "Video", - "channel": "Kanal", - "playlist": "Oynatma listesi", - "movie": "Film", - "show": "Gösteri", - "hd": "HD", - "subtitles": "Alt yazılar", - "creative_commons": "Creative Commons", - "3d": "3B", - "live": "Canlı", - "4k": "4K", - "location": "Konum", - "hdr": "HDR", - "filter": "Filtrele", + "search_filters_sort_option_relevance": "İlgi", + "search_filters_sort_option_rating": "Değerlendirme", + "search_filters_sort_option_date": "Yükleme tarihi", + "search_filters_sort_option_views": "Görüntüleme sayısı", + "search_filters_type_label": "Tür", + "search_filters_duration_label": "Süre", + "search_filters_features_label": "Özellikler", + "search_filters_sort_label": "Sıralama Ölçütü", + "search_filters_date_option_hour": "Son Saat", + "search_filters_date_option_today": "Bugün", + "search_filters_date_option_week": "Bu hafta", + "search_filters_date_option_month": "Bu ay", + "search_filters_date_option_year": "Bu yıl", + "search_filters_type_option_video": "Video", + "search_filters_type_option_channel": "Kanal", + "search_filters_type_option_playlist": "Oynatma listesi", + "search_filters_type_option_movie": "Film", + "search_filters_type_option_show": "Gösteri", + "search_filters_features_option_hd": "HD", + "search_filters_features_option_subtitles": "Alt yazılar", + "search_filters_features_option_c_commons": "Creative Commons", + "search_filters_features_option_three_d": "3B", + "search_filters_features_option_live": "Canlı", + "search_filters_features_option_four_k": "4K", + "search_filters_features_option_location": "Konum", + "search_filters_features_option_hdr": "HDR", "Current version: ": "Şu anki sürüm: ", "next_steps_error_message": "Bundan sonra şunları denemelisiniz: ", "next_steps_error_message_refresh": "Yenile", "next_steps_error_message_go_to_youtube": "YouTube'a git", - "short": "Kısa (4 dakikadan az)", - "long": "Uzun (20 dakikadan fazla)", + "search_filters_duration_option_short": "Kısa (4 dakikadan az)", + "search_filters_duration_option_long": "Uzun (20 dakikadan fazla)", "footer_documentation": "Belgelendirme", "footer_source_code": "Kaynak kodları", "footer_original_source_code": "Orijinal kaynak kodları", @@ -394,8 +392,8 @@ "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°", + "search_filters_features_option_purchased": "Satın alınan", + "search_filters_features_option_three_sixty": "360°", "videoinfo_watch_on_youTube": "YouTube'da izle", "download_subtitles": "Alt yazılar - `x` (.vtt)", "preferences_save_player_pos_label": "Oynatma konumunu kaydet: ", @@ -436,7 +434,7 @@ "crash_page_refresh": "<a href=\"`x`\">sayfayı yenilemeye</a> çalıştınız", "crash_page_switch_instance": "<a href=\"`x`\">başka bir örnek kullanmaya</a> çalıştınız", "crash_page_read_the_faq": "<a href=\"`x`\">Sık Sorulan Soruları (SSS)</a> okudunuz", - "crash_page_search_issue": "<a href=\"`x`\">Github'daki sorunlarda</a> aradınız", + "crash_page_search_issue": "<a href=\"`x`\">GitHub'daki sorunlarda</a> aradınız", "crash_page_report_issue": "Yukarıdakilerin hiçbiri yardımcı olmadıysa, lütfen <a href=\"`x`\">GitHub'da yeni bir sorun açın</a> (tercihen İngilizce) ve mesajınıza aşağıdaki metni ekleyin (bu metni ÇEVİRMEYİN):", "English (United Kingdom)": "İngilizce (Birleşik Krallık)", "Chinese": "Çince", @@ -461,5 +459,16 @@ "Portuguese (auto-generated)": "Portekizce (otomatik oluşturuldu)", "Spanish (Spain)": "İspanyolca (İspanya)", "Vietnamese (auto-generated)": "Vietnamca (otomatik oluşturuldu)", - "preferences_watch_history_label": "İzleme geçmişini etkinleştir: " + "preferences_watch_history_label": "İzleme geçmişini etkinleştir: ", + "search_message_use_another_instance": " Ayrıca <a href=\"`x`\">başka bir örnekte arayabilirsiniz</a>.", + "search_filters_type_option_all": "Herhangi bir tür", + "search_filters_duration_option_none": "Herhangi bir süre", + "search_message_no_results": "Sonuç bulunamadı.", + "search_filters_date_label": "Yükleme tarihi", + "search_filters_apply_button": "Seçili filtreleri uygula", + "search_filters_date_option_none": "Herhangi bir tarih", + "search_filters_duration_option_medium": "Orta (4 - 20 dakika)", + "search_filters_features_option_vr180": "VR180", + "search_filters_title": "Filtreler", + "search_message_change_filters_or_query": "Arama sorgunuzu genişletmeyi ve/veya filtreleri değiştirmeyi deneyin." } diff --git a/locales/uk.json b/locales/uk.json index 097752d9..dd03d559 100644 --- a/locales/uk.json +++ b/locales/uk.json @@ -21,15 +21,15 @@ "No": "Ні", "Import and Export Data": "Імпорт і експорт даних", "Import": "Імпорт", - "Import Invidious data": "Імпортувати дані Invidious", - "Import YouTube subscriptions": "Імпортувати підписки з YouTube", + "Import Invidious data": "Імпортувати JSON-дані Invidious", + "Import YouTube subscriptions": "Імпортувати підписки з YouTube чи OPML", "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", + "Export data as JSON": "Експортувати дані Invidious у форматі JSON", "Delete account?": "Видалити обліківку?", "History": "Історія", "An alternative front-end to YouTube": "Альтернативний фронтенд до YouTube", @@ -189,7 +189,7 @@ "No such user": "Недопустиме ім’я користувача", "Token is expired, please try again": "Термін дії токена закінчився, спробуйте пізніше", "English": "Англійська", - "English (auto-generated)": "Англійська (сгенеровано автоматично)", + "English (auto-generated)": "Англійська (автогенератор)", "Afrikaans": "Африкаанс", "Albanian": "Албанська", "Amharic": "Амхарська", @@ -275,7 +275,7 @@ "Somali": "Сомалійська", "Southern Sotho": "Сесото (південна сото)", "Spanish": "Іспанська", - "Spanish (Latin America)": "Испанська (Латинська Америка)", + "Spanish (Latin America)": "Іспанська (Латинська Америка)", "Sundanese": "Сунданська", "Swahili": "Суахілі", "Swedish": "Шведська", @@ -318,5 +318,173 @@ "Videos": "Відео", "Playlists": "Плейлисти", "Community": "Спільнота", - "Current version: ": "Поточна версія: " + "Current version: ": "Поточна версія: ", + "generic_views_count_0": "{{count}} перегляд", + "generic_views_count_1": "{{count}} перегляди", + "generic_views_count_2": "{{count}} переглядів", + "generic_videos_count_0": "{{count}} відео", + "generic_videos_count_1": "{{count}} відео", + "generic_videos_count_2": "{{count}} відео", + "generic_playlists_count_0": "{{count}} список відтворення", + "generic_playlists_count_1": "{{count}} списки відтворення", + "generic_playlists_count_2": "{{count}} списків відтворення", + "generic_subscribers_count_0": "{{count}} стежить", + "generic_subscribers_count_1": "{{count}} стежать", + "generic_subscribers_count_2": "{{count}} стежать", + "generic_subscriptions_count_0": "{{count}} підписка", + "generic_subscriptions_count_1": "{{count}} підписки", + "generic_subscriptions_count_2": "{{count}} підписок", + "tokens_count_0": "{{count}} токен", + "tokens_count_1": "{{count}} токени", + "tokens_count_2": "{{count}} токенів", + "subscriptions_unseen_notifs_count_0": "{{count}} нове сповіщення", + "subscriptions_unseen_notifs_count_1": "{{count}} нові сповіщення", + "subscriptions_unseen_notifs_count_2": "{{count}} нових сповіщень", + "comments_view_x_replies_0": "Переглянути {{count}} відповідь", + "comments_view_x_replies_1": "Переглянути {{count}} відповіді", + "comments_view_x_replies_2": "Переглянути {{count}} відповідей", + "generic_count_years_0": "{{count}} рік", + "generic_count_years_1": "{{count}} роки", + "generic_count_years_2": "{{count}} років", + "generic_count_weeks_0": "{{count}} тиждень", + "generic_count_weeks_1": "{{count}} тижні", + "generic_count_weeks_2": "{{count}} тижнів", + "generic_count_days_0": "{{count}} день", + "generic_count_days_1": "{{count}} дні", + "generic_count_days_2": "{{count}} днів", + "generic_count_hours_0": "{{count}} годину", + "generic_count_hours_1": "{{count}} години", + "generic_count_hours_2": "{{count}} годин", + "crash_page_switch_instance": "спробуйте <a href=\"`x`\">використати інший сервер</a>", + "crash_page_read_the_faq": "прочитайте <a href=\"`x`\">часті питання (ЧаП)</a>", + "crash_page_search_issue": "перегляньте <a href=\"`x`\">наявні обговорення на GitHub</a>", + "crash_page_report_issue": "Якщо нічого не допомогло, просимо <a href=\"`x`\">створити обговорення на GitHub</a> (бажано англійською), додавши наступний текст у повідомлення (НЕ перекладайте цього тексту):", + "Chinese (Hong Kong)": "Китайська (Гонконг)", + "Cantonese (Hong Kong)": "Кантонська (Гонконг)", + "Chinese": "Китайська", + "Chinese (China)": "Китайська (Китай)", + "Interlingue": "Інтерлінгва", + "Italian (auto-generated)": "Італійська (автогенератор)", + "Turkish (auto-generated)": "Турецька (автогенератор)", + "Vietnamese (auto-generated)": "В'єтнамська (автогенератор)", + "user_created_playlists": "Створено списків відтворення: `x`", + "user_saved_playlists": "Збережено списків відтворення: `x`", + "Video unavailable": "Відео недоступне", + "preferences_watch_history_label": "Історія переглядів: ", + "preferences_quality_dash_label": "Бажана DASH-якість відео: ", + "preferences_quality_dash_option_144p": "144p", + "preferences_vr_mode_label": "Взаємодія з 360-градусними відео (потребує WebGL): ", + "Released under the AGPLv3 on Github.": "Випущено під AGPLv3 на GitHub.", + "English (United Kingdom)": "Англійська (Сполучене Королівство)", + "English (United States)": "Англійська (США)", + "French (auto-generated)": "Французька (автогенератор)", + "German (auto-generated)": "Німецька (автогенератор)", + "Portuguese (auto-generated)": "Португальська (автогенератор)", + "Portuguese (Brazil)": "Португальська (Бразилія)", + "Russian (auto-generated)": ":^)", + "Spanish (auto-generated)": "Іспанська (автогенератор)", + "Spanish (Mexico)": "Іспанська (Мексика)", + "Spanish (Spain)": "Іспанська (Іспанія)", + "next_steps_error_message_go_to_youtube": "Перейти до YouTube", + "footer_donate_page": "Пожертвувати", + "footer_documentation": "Документація", + "footer_source_code": "Вихідний код", + "footer_original_source_code": "Оригінал вихідного коду", + "footer_modfied_source_code": "Змінений вихідний код", + "adminprefs_modified_source_code_url_label": "URL-адреса репозиторію зміненого вихідного коду", + "none": "нема", + "videoinfo_started_streaming_x_ago": "Трансляцію розпочато `x` тому", + "crash_page_you_found_a_bug": "Схоже, ви знайшли ваду в Invidious!", + "crash_page_before_reporting": "Перш ніж прозвітувати про ваду:", + "crash_page_refresh": "спробуйте <a href=\"`x`\">оновити сторінку</a>", + "preferences_quality_dash_option_auto": "Авто", + "preferences_quality_dash_option_best": "Найкраща", + "preferences_quality_dash_option_worst": "Найгірша", + "preferences_quality_dash_option_1440p": "1440p", + "preferences_quality_dash_option_1080p": "1080p", + "preferences_save_player_pos_label": "Зберегти позицію відтворення: ", + "preferences_show_nick_label": "Псевдонім угорі: ", + "Show more": "Докладніше", + "next_steps_error_message": "Після чого спробуйте: ", + "next_steps_error_message_refresh": "Оновити сторінку", + "Search": "Пошук", + "preferences_extend_desc_label": "Автоматично розширювати опис відео: ", + "preferences_category_misc": "Різноманітні параметри", + "Show less": "Коротше", + "preferences_quality_option_small": "Низька", + "preferences_quality_dash_option_240p": "240p", + "preferences_quality_option_medium": "Середня", + "preferences_quality_dash_option_4320p": "4320p", + "invidious": "Invidious", + "preferences_quality_dash_option_720p": "720p", + "preferences_quality_dash_option_360p": "360p", + "preferences_region_label": "Ваша країна: ", + "preferences_quality_option_dash": "DASH (змінна якість)", + "preferences_quality_option_hd720": "HD720", + "preferences_quality_dash_option_2160p": "2160p", + "preferences_automatic_instance_redirect_label": "Автоматична зміна сервера (redirect.invidious.io як резерв): ", + "Switch Invidious Instance": "Інший сервер Invidious", + "preferences_quality_dash_option_480p": "480p", + "Chinese (Taiwan)": "Китайська (Тайвань)", + "Dutch (auto-generated)": "Нідерландська (автогенератор)", + "Indonesian (auto-generated)": "Індонезійська (автогенератор)", + "Japanese (auto-generated)": "Японська (автогенератор)", + "Korean (auto-generated)": "Корейська (автогенератор)", + "generic_count_months_0": "{{count}} місяць", + "generic_count_months_1": "{{count}} місяці", + "generic_count_months_2": "{{count}} місяців", + "videoinfo_youTube_embed_link": "Вкласти", + "generic_count_minutes_0": "{{count}} хвилину", + "generic_count_minutes_1": "{{count}} хвилини", + "generic_count_minutes_2": "{{count}} хвилин", + "generic_count_seconds_0": "{{count}} секунду", + "generic_count_seconds_1": "{{count}} секунди", + "generic_count_seconds_2": "{{count}} секунд", + "videoinfo_watch_on_youTube": "Переглянути на YouTube", + "videoinfo_invidious_embed_link": "Вкласти посилання", + "download_subtitles": "Субтитри — `x` (.vtt)", + "comments_points_count_0": "{{count}} пункт", + "comments_points_count_1": "{{count}} пункти", + "comments_points_count_2": "{{count}} пунктів", + "search_filters_features_option_three_d": "3D", + "search_filters_features_option_location": "Геомітка", + "search_filters_duration_option_none": "Будь-які", + "search_filters_features_option_hd": "HD", + "search_message_change_filters_or_query": "Спробуйте ширший запит і/або інші фільтри.", + "search_filters_type_option_all": "Будь-що", + "search_filters_type_option_movie": "Фільм", + "search_filters_type_option_show": "Шоу", + "search_filters_duration_label": "Тривалість", + "search_filters_duration_option_short": "Короткі (до 4 хвилин)", + "search_message_no_results": "Результатів не знайдено.", + "search_filters_date_label": "Дата вивантаження", + "search_filters_date_option_none": "Будь-яка дата", + "search_filters_date_option_today": "Сьогодні", + "search_filters_date_option_week": "Цей тиждень", + "search_filters_type_label": "Тип", + "search_filters_type_option_channel": "Канал", + "search_message_use_another_instance": " Можете також <a href=\"`x`\">пошукати іншим сервером</a>.", + "search_filters_title": "Фільтри", + "search_filters_date_option_hour": "Остання година", + "search_filters_date_option_month": "Цей місяць", + "search_filters_date_option_year": "Цей рік", + "search_filters_type_option_video": "Відео", + "search_filters_type_option_playlist": "Добірка", + "search_filters_duration_option_medium": "Середні (4–20 хвилин)", + "search_filters_duration_option_long": "Довгі (понад 20 хвилин)", + "search_filters_features_label": "Особливості", + "search_filters_features_option_live": "Наживо", + "search_filters_features_option_four_k": "4K", + "search_filters_features_option_subtitles": "Субтитри", + "search_filters_features_option_c_commons": "Creative Commons", + "search_filters_features_option_three_sixty": "360°", + "search_filters_features_option_hdr": "HDR", + "search_filters_sort_label": "Спершу", + "search_filters_sort_option_date": "Нещодавні", + "search_filters_apply_button": "Застосувати фільтри", + "search_filters_features_option_vr180": "VR180", + "search_filters_features_option_purchased": "Придбано", + "search_filters_sort_option_relevance": "Відповідні", + "search_filters_sort_option_rating": "Рейтингові", + "search_filters_sort_option_views": "Популярні" } diff --git a/locales/vi.json b/locales/vi.json index a8550686..709013a2 100644 --- a/locales/vi.json +++ b/locales/vi.json @@ -138,7 +138,6 @@ "Show less": "Hiện ít hơn", "Watch on YouTube": "Xem trên YouTube", "Switch Invidious Instance": "Chuyển phiên bản Invidious", - "Broken? Try another Invidious Instance": "Bị hỏng? Hãy thử một Phiên bản Invidious khác", "Hide annotations": "Ẩn chú thích", "Show annotations": "Hiển thị chú thích", "Genre: ": "Thể loại: ", @@ -315,32 +314,32 @@ "Videos": "Video", "Playlists": "Danh sách phát", "Community": "Cộng đồng", - "relevance": "liên quan", - "rating": "Xếp hạng", - "date": "ngày", - "views": "lượt xem", - "content_type": "content_type", - "duration": "thời lượng", - "features": "đặc trưng", - "sort": "sắp xếp", - "hour": "giờ", - "today": "hôm nay", - "week": "tuần", - "month": "tháng", - "year": "năm", - "video": "video", - "channel": "kênh", - "playlist": "danh sách phát", - "movie": "bộ phim", - "show": "chỉ", - "hd": "hd", - "subtitles": "phụ đề", - "creative_commons": "Commons sáng tạo", - "3d": "3d", - "live": "trực tiếp", - "4k": "4k", - "location": "vị trí", - "hdr": "hdr", - "filter": "bộ lọc", - "Current version: ": "Phiên bản hiện tại: " + "search_filters_sort_option_relevance": "liên quan", + "search_filters_sort_option_rating": "Xếp hạng", + "search_filters_sort_option_date": "ngày", + "search_filters_sort_option_views": "lượt xem", + "search_filters_type_label": "content_type", + "search_filters_duration_label": "thời lượng", + "search_filters_features_label": "đặc trưng", + "search_filters_sort_label": "sắp xếp", + "search_filters_date_option_hour": "giờ", + "search_filters_date_option_today": "hôm nay", + "search_filters_date_option_week": "tuần", + "search_filters_date_option_month": "tháng", + "search_filters_date_option_year": "năm", + "search_filters_type_option_video": "video", + "search_filters_type_option_channel": "kênh", + "search_filters_type_option_playlist": "danh sách phát", + "search_filters_type_option_movie": "bộ phim", + "search_filters_type_option_show": "chỉ", + "search_filters_features_option_hd": "hd", + "search_filters_features_option_subtitles": "phụ đề", + "search_filters_features_option_c_commons": "Commons sáng tạo", + "search_filters_features_option_three_d": "3d", + "search_filters_features_option_live": "trực tiếp", + "search_filters_features_option_four_k": "4k", + "search_filters_features_option_location": "vị trí", + "search_filters_features_option_hdr": "hdr", + "Current version: ": "Phiên bản hiện tại: ", + "search_filters_title": "bộ lọc" } diff --git a/locales/zh-CN.json b/locales/zh-CN.json index 4b760dd3..ed180628 100644 --- a/locales/zh-CN.json +++ b/locales/zh-CN.json @@ -128,7 +128,7 @@ "subscriptions_unseen_notifs_count_0": "{{count}} 条未读通知", "search": "搜索", "Log out": "登出", - "Released under the AGPLv3 on Github.": "依据 AGPLv3 许可证发布于 Github。", + "Released under the AGPLv3 on Github.": "依据 AGPLv3 许可证发布于 GitHub。", "Source available here.": "源码可在此查看。", "View JavaScript license information.": "查看 JavaScript 协议信息。", "View privacy policy.": "查看隐私政策。", @@ -148,7 +148,6 @@ "Show less": "显示较少", "Watch on YouTube": "在 YouTube 观看", "Switch Invidious Instance": "切换 Invidious 实例", - "Broken? Try another Invidious Instance": "无法正常工作? 尝试另一个 Invidious 实例", "Hide annotations": "隐藏注释", "Show annotations": "显示注释", "Genre: ": "风格: ", @@ -345,39 +344,38 @@ "Videos": "视频", "Playlists": "播放列表", "Community": "社区", - "relevance": "相关度", - "rating": "评分", - "date": "上传日期", - "views": "观看次数", - "content_type": "类型", - "duration": "持续时间", - "features": "功能", - "sort": "排序依据", - "hour": "上个小时", - "today": "今日", - "week": "本周", - "month": "本月", - "year": "今年", - "video": "视频", - "channel": "频道", - "playlist": "播放列表", - "movie": "电影", - "show": "真人秀", - "hd": "高清", - "subtitles": "字幕", - "creative_commons": "creative_commons 许可", - "3d": "3d", - "live": "直播", - "4k": "4k", - "location": "位置", - "hdr": "hdr", - "filter": "过滤器", + "search_filters_sort_option_relevance": "相关度", + "search_filters_sort_option_rating": "评分", + "search_filters_sort_option_date": "上传日期", + "search_filters_sort_option_views": "观看次数", + "search_filters_type_label": "类型", + "search_filters_duration_label": "持续时间", + "search_filters_features_label": "功能", + "search_filters_sort_label": "排序依据", + "search_filters_date_option_hour": "上个小时", + "search_filters_date_option_today": "今日", + "search_filters_date_option_week": "本周", + "search_filters_date_option_month": "本月", + "search_filters_date_option_year": "今年", + "search_filters_type_option_video": "视频", + "search_filters_type_option_channel": "频道", + "search_filters_type_option_playlist": "播放列表", + "search_filters_type_option_movie": "电影", + "search_filters_type_option_show": "真人秀", + "search_filters_features_option_hd": "高清", + "search_filters_features_option_subtitles": "字幕", + "search_filters_features_option_c_commons": "creative_commons 许可", + "search_filters_features_option_three_d": "3d", + "search_filters_features_option_live": "直播", + "search_filters_features_option_four_k": "4k", + "search_filters_features_option_location": "位置", + "search_filters_features_option_hdr": "hdr", "Current version: ": "当前版本: ", "next_steps_error_message": "在此之后你应尝试: ", "next_steps_error_message_refresh": "刷新", "next_steps_error_message_go_to_youtube": "转到 YouTube", - "short": "短(少于4分钟)", - "long": "长(多于 20 分钟)", + "search_filters_duration_option_short": "短(少于4分钟)", + "search_filters_duration_option_long": "长(多于 20 分钟)", "footer_documentation": "文档", "footer_source_code": "源代码", "footer_modfied_source_code": "修改的源代码", @@ -391,7 +389,7 @@ "crash_page_refresh": "试着 <a href=\"`x`\">刷新页面</a>", "crash_page_switch_instance": "试着<a href=\"`x`\">使用另一个实例</a>", "crash_page_read_the_faq": "阅读<a href=\"`x`\">常见问题</a>", - "crash_page_search_issue": "搜索过 <a href=\"`x`\">Github 上的现有 issue</a>", + "crash_page_search_issue": "搜索过 <a href=\"`x`\">GitHub 上的现有 issue</a>", "crash_page_report_issue": "如果以上这些都没用的话,请<a href=\"`x`\">在 Github 上新开一个 issue</a>(最好用英语撰写),并在你的消息中包含以下文本(不要翻译该文本):", "videoinfo_invidious_embed_link": "嵌入链接", "download_subtitles": "字幕 - `x` (.vtt)", @@ -418,8 +416,8 @@ "user_created_playlists": "`x` 创建了播放列表", "user_saved_playlists": "`x` 保存了播放列表", "Video unavailable": "视频不可用", - "purchased": "已购买", - "360": "360°", + "search_filters_features_option_purchased": "已购买", + "search_filters_features_option_three_sixty": "360°", "none": "无", "preferences_save_player_pos_label": "保存播放位置: ", "Spanish (Mexico)": "西班牙语 (墨西哥)", @@ -445,5 +443,16 @@ "French (auto-generated)": "法语 (自动生成)", "Turkish (auto-generated)": "土耳其语 (自动生成)", "Spanish (Spain)": "西班牙语 (西班牙)", - "preferences_watch_history_label": "启用观看历史: " + "preferences_watch_history_label": "启用观看历史: ", + "search_message_use_another_instance": " 你也可以 <a href=\"`x`\">在另一实例上搜索</a>。", + "search_filters_title": "过滤器", + "search_filters_date_label": "上传日期", + "search_filters_apply_button": "应用所选过滤器", + "search_message_no_results": "没找到结果。", + "search_filters_duration_option_medium": "中等(4-20 分钟)", + "search_filters_date_option_none": "任意日期", + "search_message_change_filters_or_query": "尝试扩大你的搜索查询和/或更改过滤器。", + "search_filters_duration_option_none": "任意时长", + "search_filters_type_option_all": "任意类型", + "search_filters_features_option_vr180": "VR180" } diff --git a/locales/zh-TW.json b/locales/zh-TW.json index 84bd1dae..4b6fa71b 100644 --- a/locales/zh-TW.json +++ b/locales/zh-TW.json @@ -148,7 +148,6 @@ "Show less": "顯示較少", "Watch on YouTube": "在 YouTube 上觀看", "Switch Invidious Instance": "切換 Invidious 站台", - "Broken? Try another Invidious Instance": "故障了嗎?試試看其他 Invidious 站台吧", "Hide annotations": "隱藏註釋", "Show annotations": "顯示註釋", "Genre: ": "風格: ", @@ -345,39 +344,38 @@ "Videos": "影片", "Playlists": "播放清單", "Community": "社群", - "relevance": "關聯", - "rating": "評分", - "date": "日期", - "views": "檢視", - "content_type": "內容類型", - "duration": "時長", - "features": "特色", - "sort": "排序", - "hour": "小時", - "today": "今天", - "week": "週", - "month": "月", - "year": "年", - "video": "影片", - "channel": "頻道", - "playlist": "播放清單", - "movie": "電影", - "show": "秀", - "hd": "HD", - "subtitles": "字幕", - "creative_commons": "創用 CC", - "3d": "3D", - "live": "直播", - "4k": "4K", - "location": "位置", - "hdr": "HDR", - "filter": "篩選條件", + "search_filters_sort_option_relevance": "關聯", + "search_filters_sort_option_rating": "評分", + "search_filters_sort_option_date": "日期", + "search_filters_sort_option_views": "檢視", + "search_filters_type_label": "內容類型", + "search_filters_duration_label": "時長", + "search_filters_features_label": "特色", + "search_filters_sort_label": "排序", + "search_filters_date_option_hour": "小時", + "search_filters_date_option_today": "今天", + "search_filters_date_option_week": "週", + "search_filters_date_option_month": "月", + "search_filters_date_option_year": "年", + "search_filters_type_option_video": "影片", + "search_filters_type_option_channel": "頻道", + "search_filters_type_option_playlist": "播放清單", + "search_filters_type_option_movie": "電影", + "search_filters_type_option_show": "秀", + "search_filters_features_option_hd": "HD", + "search_filters_features_option_subtitles": "字幕", + "search_filters_features_option_c_commons": "創用 CC", + "search_filters_features_option_three_d": "3D", + "search_filters_features_option_live": "直播", + "search_filters_features_option_four_k": "4K", + "search_filters_features_option_location": "位置", + "search_filters_features_option_hdr": "HDR", "Current version: ": "目前版本: ", "next_steps_error_message": "之後您應該嘗試: ", "next_steps_error_message_refresh": "重新整理", "next_steps_error_message_go_to_youtube": "到 YouTube", - "short": "短(小於4分鐘)", - "long": "長(多於20分鐘)", + "search_filters_duration_option_short": "短(小於4分鐘)", + "search_filters_duration_option_long": "長(多於20分鐘)", "footer_documentation": "文件", "footer_source_code": "原始碼", "footer_original_source_code": "原本的原始碼", @@ -398,8 +396,8 @@ "preferences_quality_dash_option_240p": "240p", "preferences_quality_dash_option_144p": "144p", "invidious": "Invidious", - "purchased": "已購買", - "360": "360°", + "search_filters_features_option_purchased": "已購買", + "search_filters_features_option_three_sixty": "360°", "none": "無", "videoinfo_started_streaming_x_ago": "`x` 前開始串流", "videoinfo_watch_on_youTube": "在 YouTube 上觀看", @@ -445,5 +443,16 @@ "Portuguese (Brazil)": "葡萄牙語(巴西)", "Japanese (auto-generated)": "日語(自動產生)", "Portuguese (auto-generated)": "葡萄牙語(自動產生)", - "preferences_watch_history_label": "啟用觀看紀錄: " + "preferences_watch_history_label": "啟用觀看紀錄: ", + "search_message_change_filters_or_query": "嘗試擴大您的查詢字詞與/或變更過濾條件。", + "search_filters_apply_button": "套用選定的過濾條件", + "search_message_no_results": "找不到結果。", + "search_filters_duration_option_none": "任何時長", + "search_filters_duration_option_medium": "中等(4到20分鐘)", + "search_filters_features_option_vr180": "VR180", + "search_message_use_another_instance": " 您也可以<a href=\"`x`\">在其他站台上搜尋</a>。", + "search_filters_title": "過濾條件", + "search_filters_date_label": "上傳日期", + "search_filters_type_option_all": "任何類型", + "search_filters_date_option_none": "任何日期" } @@ -14,11 +14,11 @@ shards: exception_page: git: https://github.com/crystal-loot/exception_page.git - version: 0.2.0 + version: 0.2.2 kemal: git: https://github.com/kemalcr/kemal.git - version: 1.1.0 + version: 1.1.2 kilt: git: https://github.com/jeromegn/kilt.git @@ -18,7 +18,10 @@ dependencies: version: ~> 0.18.0 kemal: github: kemalcr/kemal - version: ~> 1.1.0 + version: ~> 1.1.2 + kilt: + github: jeromegn/kilt + version: ~> 0.6.1 protodec: github: iv-org/protodec version: ~> 0.1.4 diff --git a/spec/invidious/helpers_spec.cr b/spec/invidious/helpers_spec.cr index b2436989..5ecebef3 100644 --- a/spec/invidious/helpers_spec.cr +++ b/spec/invidious/helpers_spec.cr @@ -29,20 +29,6 @@ Spectator.describe "Helper" do end end - describe "#produce_search_params" do - it "correctly produces token for searching with specified filters" do - expect(produce_search_params).to eq("CAASAhABSAA%3D") - - expect(produce_search_params(sort: "upload_date", content_type: "video")).to eq("CAISAhABSAA%3D") - - expect(produce_search_params(content_type: "playlist")).to eq("CAASAhADSAA%3D") - - expect(produce_search_params(sort: "date", content_type: "video", features: ["hd", "cc", "purchased", "hdr"])).to eq("CAISCxABIAEwAUgByAEBSAA%3D") - - expect(produce_search_params(content_type: "channel")).to eq("CAASAhACSAA%3D") - end - end - describe "#produce_comment_continuation" do it "correctly produces a continuation token for comments" do expect(produce_comment_continuation("_cE8xSu6swE", "ADSJ_i2qvJeFtL0htmS5_K5Ctj3eGFVBMWL9Wd42o3kmUL6_mAzdLp85-liQZL0mYr_16BhaggUqX652Sv9JqV6VXinShSP-ZT6rL4NolPBaPXVtJsO5_rA_qE3GubAuLFw9uzIIXU2-HnpXbdgPLWTFavfX206hqWmmpHwUOrmxQV_OX6tYkM3ux3rPAKCDrT8eWL7MU3bLiNcnbgkW8o0h8KYLL_8BPa8LcHbTv8pAoNkjerlX1x7K4pqxaXPoyz89qNlnh6rRx6AXgAzzoHH1dmcyQ8CIBeOHg-m4i8ZxdX4dP88XWrIFg-jJGhpGP8JUMDgZgavxVx225hUEYZMyrLGler5em4FgbG62YWC51moLDLeYEA")).to eq("EkMSC19jRTh4U3U2c3dFyAEA4AEBogINKP___________wFAAMICHQgEGhdodHRwczovL3d3dy55b3V0dWJlLmNvbSIAGAYyjAMK9gJBRFNKX2kycXZKZUZ0TDBodG1TNV9LNUN0ajNlR0ZWQk1XTDlXZDQybzNrbVVMNl9tQXpkTHA4NS1saVFaTDBtWXJfMTZCaGFnZ1VxWDY1MlN2OUpxVjZWWGluU2hTUC1aVDZyTDROb2xQQmFQWFZ0SnNPNV9yQV9xRTNHdWJBdUxGdzl1eklJWFUyLUhucFhiZGdQTFdURmF2ZlgyMDZocVdtbXBId1VPcm14UVZfT1g2dFlrTTN1eDNyUEFLQ0RyVDhlV0w3TVUzYkxpTmNuYmdrVzhvMGg4S1lMTF84QlBhOExjSGJUdjhwQW9Oa2plcmxYMXg3SzRwcXhhWFBveXo4OXFObG5oNnJSeDZBWGdBenpvSEgxZG1jeVE4Q0lCZU9IZy1tNGk4WnhkWDRkUDg4WFdySUZnLWpKR2hwR1A4SlVNRGdaZ2F2eFZ4MjI1aFVFWVpNeXJMR2xlcjVlbTRGZ2JHNjJZV0M1MW1vTERMZVlFQSIPIgtfY0U4eFN1NnN3RTAAKBQ%3D") diff --git a/spec/invidious/search/iv_filters_spec.cr b/spec/invidious/search/iv_filters_spec.cr new file mode 100644 index 00000000..b0897a63 --- /dev/null +++ b/spec/invidious/search/iv_filters_spec.cr @@ -0,0 +1,371 @@ +require "../../../src/invidious/search/filters" + +require "http/params" +require "spectator" + +Spectator.configure do |config| + config.fail_blank + config.randomize +end + +FEATURES_TEXT = { + Invidious::Search::Filters::Features::Live => "live", + Invidious::Search::Filters::Features::FourK => "4k", + Invidious::Search::Filters::Features::HD => "hd", + Invidious::Search::Filters::Features::Subtitles => "subtitles", + Invidious::Search::Filters::Features::CCommons => "commons", + Invidious::Search::Filters::Features::ThreeSixty => "360", + Invidious::Search::Filters::Features::VR180 => "vr180", + Invidious::Search::Filters::Features::ThreeD => "3d", + Invidious::Search::Filters::Features::HDR => "hdr", + Invidious::Search::Filters::Features::Location => "location", + Invidious::Search::Filters::Features::Purchased => "purchased", +} + +Spectator.describe Invidious::Search::Filters do + # ------------------- + # Decode (legacy) + # ------------------- + + describe "#from_legacy_filters" do + it "Decodes channel: filter" do + query = "test channel:UC123456 request" + + fltr, chan, qury, subs = described_class.from_legacy_filters(query) + + expect(fltr).to eq(described_class.new) + expect(chan).to eq("UC123456") + expect(qury).to eq("test request") + expect(subs).to be_false + end + + it "Decodes user: filter" do + query = "user:LinusTechTips broke something (again)" + + fltr, chan, qury, subs = described_class.from_legacy_filters(query) + + expect(fltr).to eq(described_class.new) + expect(chan).to eq("LinusTechTips") + expect(qury).to eq("broke something (again)") + expect(subs).to be_false + end + + it "Decodes type: filter" do + Invidious::Search::Filters::Type.each do |value| + query = "Eiffel 65 - Blue [1 Hour] type:#{value}" + + fltr, chan, qury, subs = described_class.from_legacy_filters(query) + + expect(fltr).to eq(described_class.new(type: value)) + expect(chan).to eq("") + expect(qury).to eq("Eiffel 65 - Blue [1 Hour]") + expect(subs).to be_false + end + end + + it "Decodes content_type: filter" do + Invidious::Search::Filters::Type.each do |value| + query = "I like to watch content_type:#{value}" + + fltr, chan, qury, subs = described_class.from_legacy_filters(query) + + expect(fltr).to eq(described_class.new(type: value)) + expect(chan).to eq("") + expect(qury).to eq("I like to watch") + expect(subs).to be_false + end + end + + it "Decodes date: filter" do + Invidious::Search::Filters::Date.each do |value| + query = "This date:#{value} is old!" + + fltr, chan, qury, subs = described_class.from_legacy_filters(query) + + expect(fltr).to eq(described_class.new(date: value)) + expect(chan).to eq("") + expect(qury).to eq("This is old!") + expect(subs).to be_false + end + end + + it "Decodes duration: filter" do + Invidious::Search::Filters::Duration.each do |value| + query = "This duration:#{value} is old!" + + fltr, chan, qury, subs = described_class.from_legacy_filters(query) + + expect(fltr).to eq(described_class.new(duration: value)) + expect(chan).to eq("") + expect(qury).to eq("This is old!") + expect(subs).to be_false + end + end + + it "Decodes feature: filter" do + Invidious::Search::Filters::Features.each do |value| + string = FEATURES_TEXT[value] + query = "I like my precious feature:#{string} ^^" + + fltr, chan, qury, subs = described_class.from_legacy_filters(query) + + expect(fltr).to eq(described_class.new(features: value)) + expect(chan).to eq("") + expect(qury).to eq("I like my precious ^^") + expect(subs).to be_false + end + end + + it "Decodes features: filter" do + query = "This search has many features:vr180,cc,hdr :o" + + fltr, chan, qury, subs = described_class.from_legacy_filters(query) + + features = Invidious::Search::Filters::Features.flags(HDR, VR180, CCommons) + + expect(fltr).to eq(described_class.new(features: features)) + expect(chan).to eq("") + expect(qury).to eq("This search has many :o") + expect(subs).to be_false + end + + it "Decodes sort: filter" do + Invidious::Search::Filters::Sort.each do |value| + query = "Computer? sort:#{value} my files!" + + fltr, chan, qury, subs = described_class.from_legacy_filters(query) + + expect(fltr).to eq(described_class.new(sort: value)) + expect(chan).to eq("") + expect(qury).to eq("Computer? my files!") + expect(subs).to be_false + end + end + + it "Decodes subscriptions: filter" do + query = "enable subscriptions:true" + + fltr, chan, qury, subs = described_class.from_legacy_filters(query) + + expect(fltr).to eq(described_class.new) + expect(chan).to eq("") + expect(qury).to eq("enable") + expect(subs).to be_true + end + + it "Ignores junk data" do + query = "duration:I sort:like type:cleaning features:stuff date:up!" + + fltr, chan, qury, subs = described_class.from_legacy_filters(query) + + expect(fltr).to eq(described_class.new) + expect(chan).to eq("") + expect(qury).to eq("") + expect(subs).to be_false + end + + it "Keeps unknown keys" do + query = "to:be or:not to:be" + + fltr, chan, qury, subs = described_class.from_legacy_filters(query) + + expect(fltr).to eq(described_class.new) + expect(chan).to eq("") + expect(qury).to eq("to:be or:not to:be") + expect(subs).to be_false + end + end + + # ------------------- + # Decode (URL) + # ------------------- + + describe "#from_iv_params" do + it "Decodes type= filter" do + Invidious::Search::Filters::Type.each do |value| + params = HTTP::Params.parse("type=#{value}") + + expect(described_class.from_iv_params(params)) + .to eq(described_class.new(type: value)) + end + end + + it "Decodes date= filter" do + Invidious::Search::Filters::Date.each do |value| + params = HTTP::Params.parse("date=#{value}") + + expect(described_class.from_iv_params(params)) + .to eq(described_class.new(date: value)) + end + end + + it "Decodes duration= filter" do + Invidious::Search::Filters::Duration.each do |value| + params = HTTP::Params.parse("duration=#{value}") + + expect(described_class.from_iv_params(params)) + .to eq(described_class.new(duration: value)) + end + end + + it "Decodes features= filter (single)" do + Invidious::Search::Filters::Features.each do |value| + string = described_class.format_features(value) + params = HTTP::Params.parse("features=#{string}") + + expect(described_class.from_iv_params(params)) + .to eq(described_class.new(features: value)) + end + end + + it "Decodes features= filter (multiple - comma separated)" do + features = Invidious::Search::Filters::Features.flags(HDR, VR180, CCommons) + params = HTTP::Params.parse("features=vr180%2Ccc%2Chdr") # %2C is a comma + + expect(described_class.from_iv_params(params)) + .to eq(described_class.new(features: features)) + end + + it "Decodes features= filter (multiple - URL parameters)" do + features = Invidious::Search::Filters::Features.flags(ThreeSixty, HD, FourK) + params = HTTP::Params.parse("features=4k&features=360&features=hd") + + expect(described_class.from_iv_params(params)) + .to eq(described_class.new(features: features)) + end + + it "Decodes sort= filter" do + Invidious::Search::Filters::Sort.each do |value| + params = HTTP::Params.parse("sort=#{value}") + + expect(described_class.from_iv_params(params)) + .to eq(described_class.new(sort: value)) + end + end + + it "Ignores junk data" do + params = HTTP::Params.parse("foo=bar&sort=views&answer=42&type=channel") + + expect(described_class.from_iv_params(params)).to eq( + described_class.new( + sort: Invidious::Search::Filters::Sort::Views, + type: Invidious::Search::Filters::Type::Channel + ) + ) + end + end + + # ------------------- + # Encode (URL) + # ------------------- + + describe "#to_iv_params" do + it "Encodes date filter" do + Invidious::Search::Filters::Date.each do |value| + filters = described_class.new(date: value) + params = filters.to_iv_params + + if value.none? + expect("#{params}").to eq("") + else + expect("#{params}").to eq("date=#{value.to_s.underscore}") + end + end + end + + it "Encodes type filter" do + Invidious::Search::Filters::Type.each do |value| + filters = described_class.new(type: value) + params = filters.to_iv_params + + if value.all? + expect("#{params}").to eq("") + else + expect("#{params}").to eq("type=#{value.to_s.underscore}") + end + end + end + + it "Encodes duration filter" do + Invidious::Search::Filters::Duration.each do |value| + filters = described_class.new(duration: value) + params = filters.to_iv_params + + if value.none? + expect("#{params}").to eq("") + else + expect("#{params}").to eq("duration=#{value.to_s.underscore}") + end + end + end + + it "Encodes features filter (single)" do + Invidious::Search::Filters::Features.each do |value| + string = described_class.format_features(value) + filters = described_class.new(features: value) + + expect("#{filters.to_iv_params}") + .to eq("features=" + FEATURES_TEXT[value]) + end + end + + it "Encodes features filter (multiple)" do + features = Invidious::Search::Filters::Features.flags(Subtitles, Live, ThreeSixty) + filters = described_class.new(features: features) + + expect("#{filters.to_iv_params}") + .to eq("features=live%2Csubtitles%2C360") # %2C is a comma + end + + it "Encodes sort filter" do + Invidious::Search::Filters::Sort.each do |value| + filters = described_class.new(sort: value) + params = filters.to_iv_params + + if value.relevance? + expect("#{params}").to eq("") + else + expect("#{params}").to eq("sort=#{value.to_s.underscore}") + end + end + end + + it "Encodes multiple filters" do + filters = described_class.new( + date: Invidious::Search::Filters::Date::Today, + duration: Invidious::Search::Filters::Duration::Medium, + features: Invidious::Search::Filters::Features.flags(Location, Purchased), + sort: Invidious::Search::Filters::Sort::Relevance + ) + + params = filters.to_iv_params + + # Check the `date` param + expect(params).to have_key("date") + expect(params.fetch_all("date")).to contain_exactly("today") + + # Check the `type` param + expect(params).to_not have_key("type") + expect(params["type"]?).to be_nil + + # Check the `duration` param + expect(params).to have_key("duration") + expect(params.fetch_all("duration")).to contain_exactly("medium") + + # Check the `features` param + expect(params).to have_key("features") + expect(params.fetch_all("features")).to contain_exactly("location,purchased") + + # Check the `sort` param + expect(params).to_not have_key("sort") + expect(params["sort"]?).to be_nil + + # Check if there aren't other parameters + params.delete("date") + params.delete("duration") + params.delete("features") + + expect(params).to be_empty + end + end +end diff --git a/spec/invidious/search/query_spec.cr b/spec/invidious/search/query_spec.cr new file mode 100644 index 00000000..4853e9e9 --- /dev/null +++ b/spec/invidious/search/query_spec.cr @@ -0,0 +1,200 @@ +require "../../../src/invidious/search/filters" +require "../../../src/invidious/search/query" + +require "http/params" +require "spectator" + +Spectator.configure do |config| + config.fail_blank + config.randomize +end + +Spectator.describe Invidious::Search::Query do + describe Type::Regular do + # ------------------- + # Query parsing + # ------------------- + + it "parses query with URL prameters (q)" do + query = described_class.new( + HTTP::Params.parse("q=What+is+Love+10+hour&type=video&duration=long"), + Invidious::Search::Query::Type::Regular, nil + ) + + expect(query.type).to eq(Invidious::Search::Query::Type::Regular) + expect(query.channel).to be_empty + expect(query.text).to eq("What is Love 10 hour") + + expect(query.filters).to eq( + Invidious::Search::Filters.new( + type: Invidious::Search::Filters::Type::Video, + duration: Invidious::Search::Filters::Duration::Long + ) + ) + end + + it "parses query with URL prameters (search_query)" do + query = described_class.new( + HTTP::Params.parse("search_query=What+is+Love+10+hour&type=video&duration=long"), + Invidious::Search::Query::Type::Regular, nil + ) + + expect(query.type).to eq(Invidious::Search::Query::Type::Regular) + expect(query.channel).to be_empty + expect(query.text).to eq("What is Love 10 hour") + + expect(query.filters).to eq( + Invidious::Search::Filters.new( + type: Invidious::Search::Filters::Type::Video, + duration: Invidious::Search::Filters::Duration::Long + ) + ) + end + + it "parses query with legacy filters (q)" do + query = described_class.new( + HTTP::Params.parse("q=Nyan+cat+duration:long"), + Invidious::Search::Query::Type::Regular, nil + ) + + expect(query.type).to eq(Invidious::Search::Query::Type::Regular) + expect(query.channel).to be_empty + expect(query.text).to eq("Nyan cat") + + expect(query.filters).to eq( + Invidious::Search::Filters.new( + duration: Invidious::Search::Filters::Duration::Long + ) + ) + end + + it "parses query with legacy filters (search_query)" do + query = described_class.new( + HTTP::Params.parse("search_query=Nyan+cat+duration:long"), + Invidious::Search::Query::Type::Regular, nil + ) + + expect(query.type).to eq(Invidious::Search::Query::Type::Regular) + expect(query.channel).to be_empty + expect(query.text).to eq("Nyan cat") + + expect(query.filters).to eq( + Invidious::Search::Filters.new( + duration: Invidious::Search::Filters::Duration::Long + ) + ) + end + + it "parses query with both URL params and legacy filters" do + query = described_class.new( + HTTP::Params.parse("q=Vamos+a+la+playa+duration:long&type=Video&date=year"), + Invidious::Search::Query::Type::Regular, nil + ) + + expect(query.type).to eq(Invidious::Search::Query::Type::Regular) + expect(query.channel).to be_empty + expect(query.text).to eq("Vamos a la playa duration:long") + + expect(query.filters).to eq( + Invidious::Search::Filters.new( + type: Invidious::Search::Filters::Type::Video, + date: Invidious::Search::Filters::Date::Year + ) + ) + end + + # ------------------- + # Type switching + # ------------------- + + it "switches to channel search (URL param)" do + query = described_class.new( + HTTP::Params.parse("q=thunderbolt+4&channel=UC0vBXGSyV14uvJ4hECDOl0Q"), + Invidious::Search::Query::Type::Regular, nil + ) + + expect(query.type).to eq(Invidious::Search::Query::Type::Channel) + expect(query.channel).to eq("UC0vBXGSyV14uvJ4hECDOl0Q") + expect(query.text).to eq("thunderbolt 4") + expect(query.filters.default?).to be_true + end + + it "switches to channel search (legacy)" do + query = described_class.new( + HTTP::Params.parse("q=channel%3AUCRPdsCVuH53rcbTcEkuY4uQ+rdna3"), + Invidious::Search::Query::Type::Regular, nil + ) + + expect(query.type).to eq(Invidious::Search::Query::Type::Channel) + expect(query.channel).to eq("UCRPdsCVuH53rcbTcEkuY4uQ") + expect(query.text).to eq("rdna3") + expect(query.filters.default?).to be_true + end + + it "switches to subscriptions search" do + query = described_class.new( + HTTP::Params.parse("q=subscriptions:true+tunak+tunak+tun"), + Invidious::Search::Query::Type::Regular, nil + ) + + expect(query.type).to eq(Invidious::Search::Query::Type::Subscriptions) + expect(query.channel).to be_empty + expect(query.text).to eq("tunak tunak tun") + expect(query.filters.default?).to be_true + end + end + + describe Type::Channel do + it "ignores extra parameters" do + query = described_class.new( + HTTP::Params.parse("q=Take+on+me+channel%3AUC12345679&type=video&date=year"), + Invidious::Search::Query::Type::Channel, nil + ) + + expect(query.type).to eq(Invidious::Search::Query::Type::Channel) + expect(query.channel).to be_empty + expect(query.text).to eq("Take on me") + expect(query.filters.default?).to be_true + end + end + + describe Type::Subscriptions do + it "works" do + query = described_class.new( + HTTP::Params.parse("q=Harlem+shake&type=video&date=year"), + Invidious::Search::Query::Type::Subscriptions, nil + ) + + expect(query.type).to eq(Invidious::Search::Query::Type::Subscriptions) + expect(query.channel).to be_empty + expect(query.text).to eq("Harlem shake") + + expect(query.filters).to eq( + Invidious::Search::Filters.new( + type: Invidious::Search::Filters::Type::Video, + date: Invidious::Search::Filters::Date::Year + ) + ) + end + end + + describe Type::Playlist do + it "ignores extra parameters" do + query = described_class.new( + HTTP::Params.parse("q=Harlem+shake+type:video+date:year&channel=UC12345679"), + Invidious::Search::Query::Type::Playlist, nil + ) + + expect(query.type).to eq(Invidious::Search::Query::Type::Playlist) + expect(query.channel).to be_empty + expect(query.text).to eq("Harlem shake") + + expect(query.filters).to eq( + Invidious::Search::Filters.new( + type: Invidious::Search::Filters::Type::Video, + date: Invidious::Search::Filters::Date::Year + ) + ) + end + end +end diff --git a/spec/invidious/search/yt_filters_spec.cr b/spec/invidious/search/yt_filters_spec.cr new file mode 100644 index 00000000..bf7f21e7 --- /dev/null +++ b/spec/invidious/search/yt_filters_spec.cr @@ -0,0 +1,143 @@ +require "../../../src/invidious/search/filters" + +require "http/params" +require "spectator" + +Spectator.configure do |config| + config.fail_blank + config.randomize +end + +# Encoded filter values are extracted from the search +# page of Youtube with any browser devtools HTML inspector. + +DATE_FILTERS = { + Invidious::Search::Filters::Date::Hour => "EgIIAQ%3D%3D", + Invidious::Search::Filters::Date::Today => "EgIIAg%3D%3D", + Invidious::Search::Filters::Date::Week => "EgIIAw%3D%3D", + Invidious::Search::Filters::Date::Month => "EgIIBA%3D%3D", + Invidious::Search::Filters::Date::Year => "EgIIBQ%3D%3D", +} + +TYPE_FILTERS = { + Invidious::Search::Filters::Type::Video => "EgIQAQ%3D%3D", + Invidious::Search::Filters::Type::Channel => "EgIQAg%3D%3D", + Invidious::Search::Filters::Type::Playlist => "EgIQAw%3D%3D", + Invidious::Search::Filters::Type::Movie => "EgIQBA%3D%3D", +} + +DURATION_FILTERS = { + Invidious::Search::Filters::Duration::Short => "EgIYAQ%3D%3D", + Invidious::Search::Filters::Duration::Medium => "EgIYAw%3D%3D", + Invidious::Search::Filters::Duration::Long => "EgIYAg%3D%3D", +} + +FEATURE_FILTERS = { + Invidious::Search::Filters::Features::Live => "EgJAAQ%3D%3D", + Invidious::Search::Filters::Features::FourK => "EgJwAQ%3D%3D", + Invidious::Search::Filters::Features::HD => "EgIgAQ%3D%3D", + Invidious::Search::Filters::Features::Subtitles => "EgIoAQ%3D%3D", + Invidious::Search::Filters::Features::CCommons => "EgIwAQ%3D%3D", + Invidious::Search::Filters::Features::ThreeSixty => "EgJ4AQ%3D%3D", + Invidious::Search::Filters::Features::VR180 => "EgPQAQE%3D", + Invidious::Search::Filters::Features::ThreeD => "EgI4AQ%3D%3D", + Invidious::Search::Filters::Features::HDR => "EgPIAQE%3D", + Invidious::Search::Filters::Features::Location => "EgO4AQE%3D", + Invidious::Search::Filters::Features::Purchased => "EgJIAQ%3D%3D", +} + +SORT_FILTERS = { + Invidious::Search::Filters::Sort::Relevance => "", + Invidious::Search::Filters::Sort::Date => "CAI%3D", + Invidious::Search::Filters::Sort::Views => "CAM%3D", + Invidious::Search::Filters::Sort::Rating => "CAE%3D", +} + +Spectator.describe Invidious::Search::Filters do + # ------------------- + # Encode YT params + # ------------------- + + describe "#to_yt_params" do + sample DATE_FILTERS do |value, result| + it "Encodes upload date filter '#{value}'" do + expect(described_class.new(date: value).to_yt_params).to eq(result) + end + end + + sample TYPE_FILTERS do |value, result| + it "Encodes content type filter '#{value}'" do + expect(described_class.new(type: value).to_yt_params).to eq(result) + end + end + + sample DURATION_FILTERS do |value, result| + it "Encodes duration filter '#{value}'" do + expect(described_class.new(duration: value).to_yt_params).to eq(result) + end + end + + sample FEATURE_FILTERS do |value, result| + it "Encodes feature filter '#{value}'" do + expect(described_class.new(features: value).to_yt_params).to eq(result) + end + end + + sample SORT_FILTERS do |value, result| + it "Encodes sort filter '#{value}'" do + expect(described_class.new(sort: value).to_yt_params).to eq(result) + end + end + end + + # ------------------- + # Decode YT params + # ------------------- + + describe "#from_yt_params" do + sample DATE_FILTERS do |value, encoded| + it "Decodes upload date filter '#{value}'" do + params = HTTP::Params.parse("sp=#{encoded}") + + expect(described_class.from_yt_params(params)) + .to eq(described_class.new(date: value)) + end + end + + sample TYPE_FILTERS do |value, encoded| + it "Decodes content type filter '#{value}'" do + params = HTTP::Params.parse("sp=#{encoded}") + + expect(described_class.from_yt_params(params)) + .to eq(described_class.new(type: value)) + end + end + + sample DURATION_FILTERS do |value, encoded| + it "Decodes duration filter '#{value}'" do + params = HTTP::Params.parse("sp=#{encoded}") + + expect(described_class.from_yt_params(params)) + .to eq(described_class.new(duration: value)) + end + end + + sample FEATURE_FILTERS do |value, encoded| + it "Decodes feature filter '#{value}'" do + params = HTTP::Params.parse("sp=#{encoded}") + + expect(described_class.from_yt_params(params)) + .to eq(described_class.new(features: value)) + end + end + + sample SORT_FILTERS do |value, encoded| + it "Decodes sort filter '#{value}'" do + params = HTTP::Params.parse("sp=#{encoded}") + + expect(described_class.from_yt_params(params)) + .to eq(described_class.new(sort: value)) + end + end + end +end diff --git a/spec/spec_helper.cr b/spec/spec_helper.cr index 09320750..6c492e2f 100644 --- a/spec/spec_helper.cr +++ b/spec/spec_helper.cr @@ -8,7 +8,7 @@ require "../src/invidious/channels/*" require "../src/invidious/videos" require "../src/invidious/comments" require "../src/invidious/playlists" -require "../src/invidious/search" +require "../src/invidious/search/ctoken" require "../src/invidious/trending" require "spectator" diff --git a/src/ext/kemal_content_for.cr b/src/ext/kemal_content_for.cr new file mode 100644 index 00000000..a4f3fd96 --- /dev/null +++ b/src/ext/kemal_content_for.cr @@ -0,0 +1,16 @@ +# Overrides for Kemal's `content_for` macro in order to keep using +# kilt as it was before Kemal v1.1.1 (Kemal PR #618). + +require "kemal" +require "kilt" + +macro content_for(key, file = __FILE__) + %proc = ->() { + __kilt_io__ = IO::Memory.new + {{ yield }} + __kilt_io__.to_s + } + + CONTENT_FOR_BLOCKS[{{key}}] = Tuple.new {{file}}, %proc + nil +end diff --git a/src/invidious/helpers/static_file_handler.cr b/src/ext/kemal_static_file_handler.cr index 6ef2d74c..6ef2d74c 100644 --- a/src/invidious/helpers/static_file_handler.cr +++ b/src/ext/kemal_static_file_handler.cr diff --git a/src/invidious.cr b/src/invidious.cr index a470c6b6..dd240852 100644 --- a/src/invidious.cr +++ b/src/invidious.cr @@ -16,7 +16,13 @@ require "digest/md5" require "file_utils" + +# Require kemal, kilt, then our own overrides require "kemal" +require "kilt" +require "./ext/kemal_content_for.cr" +require "./ext/kemal_static_file_handler.cr" + require "athena-negotiation" require "openssl/hmac" require "option_parser" @@ -35,6 +41,7 @@ require "./invidious/frontend/*" require "./invidious/*" require "./invidious/channels/*" require "./invidious/user/*" +require "./invidious/search/*" require "./invidious/routes/**" require "./invidious/jobs/**" diff --git a/src/invidious/comments.cr b/src/invidious/comments.cr index 54cede37..3ae49aa6 100644 --- a/src/invidious/comments.cr +++ b/src/invidious/comments.cr @@ -580,31 +580,41 @@ def content_to_comment_html(content) if run["navigationEndpoint"]? if url = run["navigationEndpoint"]["urlEndpoint"]?.try &.["url"].as_s url = URI.parse(url) + displayed_url = url if url.host == "youtu.be" url = "/watch?v=#{url.request_target.lstrip('/')}" + displayed_url = "youtube.com#{url}" elsif url.host.nil? || url.host.not_nil!.ends_with?("youtube.com") if url.path == "/redirect" # 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"]? || "" + displayed_url = url else url = url.request_target + displayed_url = "youtube.com#{url}" end end - text = %(<a href="#{url}">#{text}</a>) + text = %(<a href="#{url}">#{reduce_uri(displayed_url)}</a>) elsif watch_endpoint = run["navigationEndpoint"]["watchEndpoint"]? length_seconds = watch_endpoint["startTimeSeconds"]? video_id = watch_endpoint["videoId"].as_s - if length_seconds && length_seconds.as_i > 0 + if length_seconds && length_seconds.as_i >= 0 text = %(<a href="javascript:void(0)" data-onclick="jump_to_time" data-jump-time="#{length_seconds}">#{text}</a>) else - text = %(<a href="/watch?v=#{video_id}">#{text}</a>) + text = %(<a href="/watch?v=#{video_id}">#{"youtube.com/watch?v=#{video_id}"}</a>) end elsif url = run.dig?("navigationEndpoint", "commandMetadata", "webCommandMetadata", "url").try &.as_s - text = %(<a href="#{url}">#{text}</a>) + if text.starts_with?(/\s?@/) + # Handle "pings" in comments differently + # See: https://github.com/iv-org/invidious/issues/3038 + text = %(<a href="#{url}">#{text}</a>) + else + text = %(<a href="#{url}">#{reduce_uri(url)}</a>) + end end end diff --git a/src/invidious/exceptions.cr b/src/invidious/exceptions.cr index 490d98cd..bfaa3fd5 100644 --- a/src/invidious/exceptions.cr +++ b/src/invidious/exceptions.cr @@ -1,3 +1,11 @@ +# Exception used to hold the bogus UCID during a channel search. +class ChannelSearchException < InfoException + getter channel : String + + def initialize(@channel) + end +end + # Exception used to hold the name of the missing item # Should be used in all parsing functions class BrokenTubeException < Exception diff --git a/src/invidious/frontend/misc.cr b/src/invidious/frontend/misc.cr new file mode 100644 index 00000000..43ba9f5c --- /dev/null +++ b/src/invidious/frontend/misc.cr @@ -0,0 +1,14 @@ +module Invidious::Frontend::Misc + extend self + + def redirect_url(env : HTTP::Server::Context) + prefs = env.get("preferences").as(Preferences) + + if prefs.automatic_instance_redirect + current_page = env.get?("current_page").as(String) + redirect_url = "/redirect?referer=#{current_page}" + else + redirect_url = "https://redirect.invidious.io#{env.request.resource}" + end + end +end diff --git a/src/invidious/frontend/search_filters.cr b/src/invidious/frontend/search_filters.cr new file mode 100644 index 00000000..8ac0af2e --- /dev/null +++ b/src/invidious/frontend/search_filters.cr @@ -0,0 +1,135 @@ +module Invidious::Frontend::SearchFilters + extend self + + # Generate the search filters collapsable widget. + def generate(filters : Search::Filters, query : String, page : Int, locale : String) : String + return String.build(8000) do |str| + str << "<div id='filters'>\n" + str << "\t<details id='filters-collapse'>" + str << "\t\t<summary>" << translate(locale, "search_filters_title") << "</summary>\n" + + str << "\t\t<div id='filters-box'><form action='/search' method='get'>\n" + + str << "\t\t\t<input type='hidden' name='q' value='" << HTML.escape(query) << "'>\n" + str << "\t\t\t<input type='hidden' name='page' value='" << page << "'>\n" + + str << "\t\t\t<div id='filters-flex'>" + + filter_wrapper(date) + filter_wrapper(type) + filter_wrapper(duration) + filter_wrapper(features) + filter_wrapper(sort) + + str << "\t\t\t</div>\n" + + str << "\t\t\t<div id='filters-apply'>" + str << "<button type='submit' class=\"pure-button pure-button-primary\">" + str << translate(locale, "search_filters_apply_button") + str << "</button></div>\n" + + str << "\t\t</form></div>\n" + + str << "\t</details>\n" + str << "</div>\n" + end + end + + # Generate wrapper HTML (`<div>`, filter name, etc...) around the + # `<input>` elements of a search filter + macro filter_wrapper(name) + str << "\t\t\t\t<div class=\"filter-column\"><fieldset>\n" + + str << "\t\t\t\t\t<legend><div class=\"filter-name underlined\">" + str << translate(locale, "search_filters_{{name}}_label") + str << "</div></legend>\n" + + str << "\t\t\t\t\t<div class=\"filter-options\">\n" + make_{{name}}_filter_options(str, filters.{{name}}, locale) + str << "\t\t\t\t\t</div>" + + str << "\t\t\t\t</fieldset></div>\n" + end + + # Generates the HTML for the list of radio buttons of the "date" search filter + def make_date_filter_options(str : String::Builder, value : Search::Filters::Date, locale : String) + {% for value in Invidious::Search::Filters::Date.constants %} + {% date = value.underscore %} + + str << "\t\t\t\t\t\t<div>" + str << "<input type='radio' name='date' id='filter-date-{{date}}' value='{{date}}'" + str << " checked" if value.{{date}}? + str << '>' + + str << "<label for='filter-date-{{date}}'>" + str << translate(locale, "search_filters_date_option_{{date}}") + str << "</label></div>\n" + {% end %} + end + + # Generates the HTML for the list of radio buttons of the "type" search filter + def make_type_filter_options(str : String::Builder, value : Search::Filters::Type, locale : String) + {% for value in Invidious::Search::Filters::Type.constants %} + {% type = value.underscore %} + + str << "\t\t\t\t\t\t<div>" + str << "<input type='radio' name='type' id='filter-type-{{type}}' value='{{type}}'" + str << " checked" if value.{{type}}? + str << '>' + + str << "<label for='filter-type-{{type}}'>" + str << translate(locale, "search_filters_type_option_{{type}}") + str << "</label></div>\n" + {% end %} + end + + # Generates the HTML for the list of radio buttons of the "duration" search filter + def make_duration_filter_options(str : String::Builder, value : Search::Filters::Duration, locale : String) + {% for value in Invidious::Search::Filters::Duration.constants %} + {% duration = value.underscore %} + + str << "\t\t\t\t\t\t<div>" + str << "<input type='radio' name='duration' id='filter-duration-{{duration}}' value='{{duration}}'" + str << " checked" if value.{{duration}}? + str << '>' + + str << "<label for='filter-duration-{{duration}}'>" + str << translate(locale, "search_filters_duration_option_{{duration}}") + str << "</label></div>\n" + {% end %} + end + + # Generates the HTML for the list of checkboxes of the "features" search filter + def make_features_filter_options(str : String::Builder, value : Search::Filters::Features, locale : String) + {% for value in Invidious::Search::Filters::Features.constants %} + {% if value.stringify != "All" && value.stringify != "None" %} + {% feature = value.underscore %} + + str << "\t\t\t\t\t\t<div>" + str << "<input type='checkbox' name='features' id='filter-feature-{{feature}}' value='{{feature}}'" + str << " checked" if value.{{feature}}? + str << '>' + + str << "<label for='filter-feature-{{feature}}'>" + str << translate(locale, "search_filters_features_option_{{feature}}") + str << "</label></div>\n" + {% end %} + {% end %} + end + + # Generates the HTML for the list of radio buttons of the "sort" search filter + def make_sort_filter_options(str : String::Builder, value : Search::Filters::Sort, locale : String) + {% for value in Invidious::Search::Filters::Sort.constants %} + {% sort = value.underscore %} + + str << "\t\t\t\t\t\t<div>" + str << "<input type='radio' name='sort' id='filter-sort-{{sort}}' value='{{sort}}'" + str << " checked" if value.{{sort}}? + str << '>' + + str << "<label for='filter-sort-{{sort}}'>" + str << translate(locale, "search_filters_sort_option_{{sort}}") + str << "</label></div>\n" + {% end %} + end +end diff --git a/src/invidious/helpers/errors.cr b/src/invidious/helpers/errors.cr index 6155e561..2eab6263 100644 --- a/src/invidious/helpers/errors.cr +++ b/src/invidious/helpers/errors.cr @@ -49,7 +49,7 @@ def error_template_helper(env : HTTP::Server::Context, status_code : Int32, exce issue_template += github_details("Backtrace", HTML.escape(exception.inspect_with_backtrace)) # URLs for the error message below - url_faq = "https://github.com/iv-org/documentation/blob/master/FAQ.md" + url_faq = "https://github.com/iv-org/documentation/blob/master/docs/faq.md" url_search_issues = "https://github.com/iv-org/invidious/issues" url_switch = "https://redirect.invidious.io" + env.request.resource diff --git a/src/invidious/helpers/i18n.cr b/src/invidious/helpers/i18n.cr index 39e183f2..982b97d8 100644 --- a/src/invidious/helpers/i18n.cr +++ b/src/invidious/helpers/i18n.cr @@ -29,10 +29,10 @@ LOCALES_LIST = { "pt-BR" => "Português Brasileiro", # Portuguese (Brazil) "pt-PT" => "Português de Portugal", # Portuguese (Portugal) "ro" => "Română", # Romanian - "ru" => "русский", # Russian + "ru" => "Русский", # Russian "sq" => "Shqip", # Albanian - "sr" => "srpski (latinica)", # Serbian (Latin) - "sr_Cyrl" => "српски (ћирилица)", # Serbian (Cyrillic) + "sr" => "Srpski (latinica)", # Serbian (Latin) + "sr_Cyrl" => "Српски (ћирилица)", # Serbian (Cyrillic) "sv-SE" => "Svenska", # Swedish "tr" => "Türkçe", # Turkish "uk" => "Українська", # Ukrainian diff --git a/src/invidious/helpers/macros.cr b/src/invidious/helpers/macros.cr index 75df1612..43e7171b 100644 --- a/src/invidious/helpers/macros.cr +++ b/src/invidious/helpers/macros.cr @@ -48,13 +48,19 @@ module JSON::Serializable end end -macro templated(filename, template = "template", navbar_search = true) +macro templated(_filename, template = "template", navbar_search = true) navbar_search = {{navbar_search}} - render "src/invidious/views/#{{{filename}}}.ecr", "src/invidious/views/#{{{template}}}.ecr" + + {{ filename = "src/invidious/views/" + _filename + ".ecr" }} + {{ layout = "src/invidious/views/" + template + ".ecr" }} + + __content_filename__ = {{filename}} + content = Kilt.render({{filename}}) + Kilt.render({{layout}}) end macro rendered(filename) - render "src/invidious/views/#{{{filename}}}.ecr" + Kilt.render("src/invidious/views/#{{{filename}}}.ecr") end # Similar to Kemals halt method but works in a diff --git a/src/invidious/helpers/utils.cr b/src/invidious/helpers/utils.cr index c1dc17db..8ae5034a 100644 --- a/src/invidious/helpers/utils.cr +++ b/src/invidious/helpers/utils.cr @@ -383,3 +383,11 @@ def fetch_random_instance return filtered_instance_list.sample(1)[0] end + +def reduce_uri(uri : URI | String, max_length : Int32 = 50, suffix : String = "…") : String + str = uri.to_s.sub(/^https?:\/\//, "") + if str.size > max_length + str = "#{str[0, max_length]}#{suffix}" + end + return str +end diff --git a/src/invidious/routes/api/manifest.cr b/src/invidious/routes/api/manifest.cr index ca429df5..8bc36946 100644 --- a/src/invidious/routes/api/manifest.cr +++ b/src/invidious/routes/api/manifest.cr @@ -56,12 +56,15 @@ module Invidious::Routes::API::Manifest xml.element("Period") do i = 0 - {"audio/mp4", "audio/webm"}.each do |mime_type| + {"audio/mp4"}.each do |mime_type| mime_streams = audio_streams.select { |stream| stream["mimeType"].as_s.starts_with? mime_type } next if mime_streams.empty? xml.element("AdaptationSet", id: i, mimeType: mime_type, startWithSAP: 1, subsegmentAlignment: true) do mime_streams.each do |fmt| + # OTF streams aren't supported yet (See https://github.com/TeamNewPipe/NewPipe/issues/2415) + next if !(fmt.has_key?("indexRange") && fmt.has_key?("initRange")) + codecs = fmt["mimeType"].as_s.split("codecs=")[1].strip('"') bandwidth = fmt["bitrate"].as_i itag = fmt["itag"].as_i @@ -83,13 +86,16 @@ module Invidious::Routes::API::Manifest potential_heights = {4320, 2160, 1440, 1080, 720, 480, 360, 240, 144} - {"video/mp4", "video/webm"}.each do |mime_type| + {"video/mp4"}.each do |mime_type| mime_streams = video_streams.select { |stream| stream["mimeType"].as_s.starts_with? mime_type } next if mime_streams.empty? heights = [] of Int32 xml.element("AdaptationSet", id: i, mimeType: mime_type, startWithSAP: 1, subsegmentAlignment: true, scanType: "progressive") do mime_streams.each do |fmt| + # OTF streams aren't supported yet (See https://github.com/TeamNewPipe/NewPipe/issues/2415) + next if !(fmt.has_key?("indexRange") && fmt.has_key?("initRange")) + codecs = fmt["mimeType"].as_s.split("codecs=")[1].strip('"') bandwidth = fmt["bitrate"].as_i itag = fmt["itag"].as_i diff --git a/src/invidious/routes/api/v1/channels.cr b/src/invidious/routes/api/v1/channels.cr index c4d6643a..8650976d 100644 --- a/src/invidious/routes/api/v1/channels.cr +++ b/src/invidious/routes/api/v1/channels.cr @@ -251,18 +251,22 @@ module Invidious::Routes::API::V1::Channels def self.search(env) locale = env.get("preferences").as(Preferences).locale + region = env.params.query["region"]? env.response.content_type = "application/json" - ucid = env.params.url["ucid"] + query = Invidious::Search::Query.new(env.params.query, :channel, region) - query = env.params.query["q"]? - query ||= "" + # Required because we can't (yet) pass multiple parameter to the + # `Search::Query` initializer (in this case, an URL segment) + query.channel = env.params.url["ucid"] - page = env.params.query["page"]?.try &.to_i? - page ||= 1 + begin + search_results = query.process + rescue ex + return error_json(400, ex) + end - search_results = channel_search(query, page, ucid) JSON.build do |json| json.array do search_results.each do |item| diff --git a/src/invidious/routes/api/v1/search.cr b/src/invidious/routes/api/v1/search.cr index 5666460d..21451d33 100644 --- a/src/invidious/routes/api/v1/search.cr +++ b/src/invidious/routes/api/v1/search.cr @@ -5,34 +5,14 @@ module Invidious::Routes::API::V1::Search env.response.content_type = "application/json" - query = env.params.query["q"]? - query ||= "" - - page = env.params.query["page"]?.try &.to_i? - page ||= 1 - - sort_by = env.params.query["sort_by"]?.try &.downcase - sort_by ||= "relevance" - - date = env.params.query["date"]?.try &.downcase - date ||= "" - - duration = env.params.query["duration"]?.try &.downcase - duration ||= "" - - features = env.params.query["features"]?.try &.split(",").map(&.downcase) - features ||= [] of String - - content_type = env.params.query["type"]?.try &.downcase - content_type ||= "video" + query = Invidious::Search::Query.new(env.params.query, :regular, region) begin - search_params = produce_search_params(page, sort_by, date, content_type, duration, features) + search_results = query.process rescue ex return error_json(400, ex) end - search_results = search(query, search_params, region) JSON.build do |json| json.array do search_results.each do |item| diff --git a/src/invidious/routes/playlists.cr b/src/invidious/routes/playlists.cr index dbeb4f97..de981d81 100644 --- a/src/invidious/routes/playlists.cr +++ b/src/invidious/routes/playlists.cr @@ -212,7 +212,10 @@ module Invidious::Routes::Playlists end def self.add_playlist_items_page(env) - locale = env.get("preferences").as(Preferences).locale + prefs = env.get("preferences").as(Preferences) + locale = prefs.locale + + region = env.params.query["region"]? || prefs.region user = env.get? "user" sid = env.get? "sid" @@ -236,15 +239,10 @@ module Invidious::Routes::Playlists return env.redirect referer end - query = env.params.query["q"]? - if query - begin - search_query, items, operators = process_search_query(query, page, user, region: nil) - videos = items.select(SearchVideo).map(&.as(SearchVideo)) - rescue ex - videos = [] of SearchVideo - end - else + begin + query = Invidious::Search::Query.new(env.params.query, :playlist, region) + videos = query.process.select(SearchVideo).map(&.as(SearchVideo)) + rescue ex videos = [] of SearchVideo end diff --git a/src/invidious/routes/search.cr b/src/invidious/routes/search.cr index 3f4c7e5e..e60d0081 100644 --- a/src/invidious/routes/search.cr +++ b/src/invidious/routes/search.cr @@ -37,37 +37,29 @@ module Invidious::Routes::Search end def self.search(env) - locale = env.get("preferences").as(Preferences).locale - region = env.params.query["region"]? + prefs = env.get("preferences").as(Preferences) + locale = prefs.locale - query = env.params.query["search_query"]? - query ||= env.params.query["q"]? + region = env.params.query["region"]? || prefs.region + + query = Invidious::Search::Query.new(env.params.query, :regular, region) - if !query || query.empty? + if query.empty? # Display the full page search box implemented in #1977 env.set "search", "" templated "search_homepage", navbar_search: false else - page = env.params.query["page"]?.try &.to_i? - page ||= 1 - user = env.get? "user" begin - search_query, videos, operators = process_search_query(query, page, user, region: region) + videos = query.process rescue ex : ChannelSearchException return error_template(404, "Unable to find channel with id of '#{HTML.escape(ex.channel)}'. Are you sure that's an actual channel id? It should look like 'UC4QobU6STFB0P71PMvOGN5A'.") rescue ex return error_template(500, ex) end - operator_hash = {} of String => String - operators.each do |operator| - key, value = operator.downcase.split(":") - operator_hash[key] = value - end - - env.set "search", query + env.set "search", query.text templated "search" end end diff --git a/src/invidious/search.cr b/src/invidious/search.cr deleted file mode 100644 index ae106bf6..00000000 --- a/src/invidious/search.cr +++ /dev/null @@ -1,254 +0,0 @@ -class ChannelSearchException < InfoException - getter channel : String - - def initialize(@channel) - end -end - -def channel_search(query, page, channel) : Array(SearchItem) - response = YT_POOL.client &.get("/channel/#{channel}") - - if response.status_code == 404 - response = YT_POOL.client &.get("/user/#{channel}") - response = YT_POOL.client &.get("/c/#{channel}") if response.status_code == 404 - initial_data = extract_initial_data(response.body) - ucid = initial_data.dig?("header", "c4TabbedHeaderRenderer", "channelId").try(&.as_s?) - raise ChannelSearchException.new(channel) if !ucid - else - ucid = channel - end - - continuation = produce_channel_search_continuation(ucid, query, page) - response_json = YoutubeAPI.browse(continuation) - - continuation_items = response_json["onResponseReceivedActions"]? - .try &.[0]["appendContinuationItemsAction"]["continuationItems"] - - return [] of SearchItem if !continuation_items - - items = [] of SearchItem - continuation_items.as_a.select(&.as_h.has_key?("itemSectionRenderer")).each do |item| - extract_item(item["itemSectionRenderer"]["contents"].as_a[0]).try { |t| items << t } - end - - return items -end - -def search(query, search_params = produce_search_params(content_type: "all"), region = nil) : Array(SearchItem) - return [] of SearchItem if query.empty? - - client_config = YoutubeAPI::ClientConfig.new(region: region) - initial_data = YoutubeAPI.search(query, search_params, client_config: client_config) - - return extract_items(initial_data) -end - -def produce_search_params(page = 1, sort : String = "relevance", date : String = "", content_type : String = "", - duration : String = "", features : Array(String) = [] of String) - object = { - "1:varint" => 0_i64, - "2:embedded" => {} of String => Int64, - "9:varint" => ((page - 1) * 20).to_i64, - } - - case sort - when "relevance" - object["1:varint"] = 0_i64 - when "rating" - object["1:varint"] = 1_i64 - when "upload_date", "date" - object["1:varint"] = 2_i64 - when "view_count", "views" - object["1:varint"] = 3_i64 - else - raise "No sort #{sort}" - end - - case date - when "hour" - object["2:embedded"].as(Hash)["1:varint"] = 1_i64 - when "today" - object["2:embedded"].as(Hash)["1:varint"] = 2_i64 - when "week" - object["2:embedded"].as(Hash)["1:varint"] = 3_i64 - when "month" - object["2:embedded"].as(Hash)["1:varint"] = 4_i64 - when "year" - object["2:embedded"].as(Hash)["1:varint"] = 5_i64 - else nil # Ignore - end - - case content_type - when "video" - object["2:embedded"].as(Hash)["2:varint"] = 1_i64 - when "channel" - object["2:embedded"].as(Hash)["2:varint"] = 2_i64 - when "playlist" - object["2:embedded"].as(Hash)["2:varint"] = 3_i64 - when "movie" - object["2:embedded"].as(Hash)["2:varint"] = 4_i64 - when "show" - object["2:embedded"].as(Hash)["2:varint"] = 5_i64 - when "all" - # - else - object["2:embedded"].as(Hash)["2:varint"] = 1_i64 - end - - case duration - when "short" - object["2:embedded"].as(Hash)["3:varint"] = 1_i64 - when "long" - object["2:embedded"].as(Hash)["3:varint"] = 2_i64 - else nil # Ignore - end - - features.each do |feature| - case feature - when "hd" - object["2:embedded"].as(Hash)["4:varint"] = 1_i64 - when "subtitles" - object["2:embedded"].as(Hash)["5:varint"] = 1_i64 - when "creative_commons", "cc" - object["2:embedded"].as(Hash)["6:varint"] = 1_i64 - when "3d" - object["2:embedded"].as(Hash)["7:varint"] = 1_i64 - when "live", "livestream" - object["2:embedded"].as(Hash)["8:varint"] = 1_i64 - when "purchased" - object["2:embedded"].as(Hash)["9:varint"] = 1_i64 - when "4k" - object["2:embedded"].as(Hash)["14:varint"] = 1_i64 - when "360" - object["2:embedded"].as(Hash)["15:varint"] = 1_i64 - when "location" - object["2:embedded"].as(Hash)["23:varint"] = 1_i64 - when "hdr" - object["2:embedded"].as(Hash)["25:varint"] = 1_i64 - else nil # Ignore - end - end - - if object["2:embedded"].as(Hash).empty? - object.delete("2:embedded") - end - - params = 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 params -end - -def produce_channel_search_continuation(ucid, query, page) - if page <= 1 - idx = 0_i64 - else - idx = 30_i64 * (page - 1) - end - - object = { - "80226972:embedded" => { - "2:string" => ucid, - "3:base64" => { - "2:string" => "search", - "6:varint" => 1_i64, - "7:varint" => 1_i64, - "12:varint" => 1_i64, - "15:base64" => { - "3:varint" => idx, - }, - "23:varint" => 0_i64, - }, - "11:string" => query, - "35:string" => "browse-feed#{ucid}search", - }, - } - - 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 - -def process_search_query(query, page, user, region) - if user - user = user.as(Invidious::User) - view_name = "subscriptions_#{sha256(user.email)}" - end - - channel = nil - content_type = "all" - date = "" - duration = "" - features = [] of String - sort = "relevance" - subscriptions = nil - - operators = query.split(" ").select(&.match(/\w+:[\w,]+/)) - operators.each do |operator| - key, value = operator.downcase.split(":") - - case key - when "channel", "user" - channel = operator.split(":")[-1] - when "content_type", "type" - content_type = value - when "date" - date = value - when "duration" - duration = value - when "feature", "features" - features = value.split(",") - when "sort" - sort = value - when "subscriptions" - subscriptions = value == "true" - else - operators.delete(operator) - end - end - - search_query = (query.split(" ") - operators).join(" ") - - if channel - items = channel_search(search_query, page, channel) - elsif subscriptions - if view_name - items = PG_DB.query_all("SELECT id,title,published,updated,ucid,author,length_seconds FROM ( - SELECT *, - to_tsvector(#{view_name}.title) || - to_tsvector(#{view_name}.author) - as document - FROM #{view_name} - ) v_search WHERE v_search.document @@ plainto_tsquery($1) LIMIT 20 OFFSET $2;", search_query, (page - 1) * 20, as: ChannelVideo) - else - items = [] of ChannelVideo - end - else - search_params = produce_search_params(page: page, sort: sort, date: date, content_type: content_type, - duration: duration, features: features) - - items = search(search_query, search_params, region) - end - - # Light processing to flatten search results out of Categories. - # They should ideally be supported in the future. - items_without_category = [] of SearchItem | ChannelVideo - items.each do |i| - if i.is_a? Category - i.contents.each do |nest_i| - if !nest_i.is_a? Video - items_without_category << nest_i - end - end - else - items_without_category << i - end - end - - {search_query, items_without_category, operators} -end diff --git a/src/invidious/search/ctoken.cr b/src/invidious/search/ctoken.cr new file mode 100644 index 00000000..161065e0 --- /dev/null +++ b/src/invidious/search/ctoken.cr @@ -0,0 +1,32 @@ +def produce_channel_search_continuation(ucid, query, page) + if page <= 1 + idx = 0_i64 + else + idx = 30_i64 * (page - 1) + end + + object = { + "80226972:embedded" => { + "2:string" => ucid, + "3:base64" => { + "2:string" => "search", + "6:varint" => 1_i64, + "7:varint" => 1_i64, + "12:varint" => 1_i64, + "15:base64" => { + "3:varint" => idx, + }, + "23:varint" => 0_i64, + }, + "11:string" => query, + "35:string" => "browse-feed#{ucid}search", + }, + } + + 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/search/filters.cr b/src/invidious/search/filters.cr new file mode 100644 index 00000000..c2b5c758 --- /dev/null +++ b/src/invidious/search/filters.cr @@ -0,0 +1,376 @@ +require "protodec/utils" +require "http/params" + +module Invidious::Search + struct Filters + # Values correspond to { "2:embedded": { "1:varint": <X> }} + # except for "None" which is only used by us (= nothing selected) + enum Date + None = 0 + Hour = 1 + Today = 2 + Week = 3 + Month = 4 + Year = 5 + end + + # Values correspond to { "2:embedded": { "2:varint": <X> }} + # except for "All" which is only used by us (= nothing selected) + enum Type + All = 0 + Video = 1 + Channel = 2 + Playlist = 3 + Movie = 4 + + # Has it been removed? + # (Not available on youtube's UI) + Show = 5 + end + + # Values correspond to { "2:embedded": { "3:varint": <X> }} + # except for "None" which is only used by us (= nothing selected) + enum Duration + None = 0 + Short = 1 # "Under 4 minutes" + Long = 2 # "Over 20 minutes" + Medium = 3 # "4 - 20 minutes" + end + + # Note: flag enums automatically generate + # "none" and "all" members + @[Flags] + enum Features + Live + FourK # "4K" + HD + Subtitles # "Subtitles/CC" + CCommons # "Creative Commons" + ThreeSixty # "360°" + VR180 + ThreeD # "3D" + HDR + Location + Purchased + end + + # Values correspond to { "1:varint": <X> } + enum Sort + Relevance = 0 + Rating = 1 + Date = 2 + Views = 3 + end + + # Parameters are sorted as on Youtube + property date : Date + property type : Type + property duration : Duration + property features : Features + property sort : Sort + + def initialize( + *, # All parameters must be named + @date : Date = Date::None, + @type : Type = Type::All, + @duration : Duration = Duration::None, + @features : Features = Features::None, + @sort : Sort = Sort::Relevance + ) + end + + def default? : Bool + return @date.none? && @type.all? && @duration.none? && \ + @features.none? && @sort.relevance? + end + + # ------------------- + # Invidious params + # ------------------- + + def self.parse_features(raw : Array(String)) : Features + # Initialize return variable + features = Features.new(0) + + raw.each do |ft| + case ft.downcase + when "live", "livestream" + features = features | Features::Live + when "4k" then features = features | Features::FourK + when "hd" then features = features | Features::HD + when "subtitles" then features = features | Features::Subtitles + when "creative_commons", "commons", "cc" + features = features | Features::CCommons + when "360" then features = features | Features::ThreeSixty + when "vr180" then features = features | Features::VR180 + when "3d" then features = features | Features::ThreeD + when "hdr" then features = features | Features::HDR + when "location" then features = features | Features::Location + when "purchased" then features = features | Features::Purchased + end + end + + return features + end + + def self.format_features(features : Features) : String + # Directly return an empty string if there are no features + return "" if features.none? + + # Initialize return variable + str = [] of String + + str << "live" if features.live? + str << "4k" if features.four_k? + str << "hd" if features.hd? + str << "subtitles" if features.subtitles? + str << "commons" if features.c_commons? + str << "360" if features.three_sixty? + str << "vr180" if features.vr180? + str << "3d" if features.three_d? + str << "hdr" if features.hdr? + str << "location" if features.location? + str << "purchased" if features.purchased? + + return str.join(',') + end + + def self.from_legacy_filters(str : String) : {Filters, String, String, Bool} + # Split search query on spaces + members = str.split(' ') + + # Output variables + channel = "" + filters = Filters.new + subscriptions = false + + # Array to hold the non-filter members + query = [] of String + + # Parse! + members.each do |substr| + # Separator operators + operators = substr.split(':') + + case operators[0] + when "user", "channel" + next if operators.size != 2 + channel = operators[1] + # + when "type", "content_type" + next if operators.size != 2 + type = Type.parse?(operators[1]) + filters.type = type if !type.nil? + # + when "date" + next if operators.size != 2 + date = Date.parse?(operators[1]) + filters.date = date if !date.nil? + # + when "duration" + next if operators.size != 2 + duration = Duration.parse?(operators[1]) + filters.duration = duration if !duration.nil? + # + when "feature", "features" + next if operators.size != 2 + features = parse_features(operators[1].split(',')) + filters.features = features if !features.nil? + # + when "sort" + next if operators.size != 2 + sort = Sort.parse?(operators[1]) + filters.sort = sort if !sort.nil? + # + when "subscriptions" + next if operators.size != 2 + subscriptions = {"true", "on", "yes", "1"}.any?(&.== operators[1]) + # + else + query << substr + end + end + + # Re-assemble query (without filters) + cleaned_query = query.join(' ') + + return {filters, channel, cleaned_query, subscriptions} + end + + def self.from_iv_params(params : HTTP::Params) : Filters + # Temporary variables + filters = Filters.new + + if type = params["type"]? + filters.type = Type.parse?(type) || Type::All + params.delete("type") + end + + if date = params["date"]? + filters.date = Date.parse?(date) || Date::None + params.delete("date") + end + + if duration = params["duration"]? + filters.duration = Duration.parse?(duration) || Duration::None + params.delete("duration") + end + + features = params.fetch_all("features") + if !features.empty? + # Un-array input so it can be treated as a comma-separated list + features = features[0].split(',') if features.size == 1 + + filters.features = parse_features(features) || Features::None + params.delete_all("features") + end + + if sort = params["sort"]? + filters.sort = Sort.parse?(sort) || Sort::Relevance + params.delete("sort") + end + + return filters + end + + def to_iv_params : HTTP::Params + # Temporary variables + raw_params = {} of String => Array(String) + + raw_params["date"] = [@date.to_s.underscore] if !@date.none? + raw_params["type"] = [@type.to_s.underscore] if !@type.all? + raw_params["sort"] = [@sort.to_s.underscore] if !@sort.relevance? + + if !@duration.none? + raw_params["duration"] = [@duration.to_s.underscore] + end + + if !@features.none? + raw_params["features"] = [Filters.format_features(@features)] + end + + return HTTP::Params.new(raw_params) + end + + # ------------------- + # Youtube params + # ------------------- + + # Produce the youtube search parameters for the + # innertube API (base64-encoded protobuf object). + def to_yt_params(page : Int = 1) : String + # Initialize the embedded protobuf object + embedded = {} of String => Int64 + + # Add these field only if associated parameter is selected + embedded["1:varint"] = @date.to_i64 if !@date.none? + embedded["2:varint"] = @type.to_i64 if !@type.all? + embedded["3:varint"] = @duration.to_i64 if !@duration.none? + + if !@features.none? + # All features have a value of "1" when enabled, and + # the field is omitted when the feature is no selected. + embedded["4:varint"] = 1_i64 if @features.includes?(Features::HD) + embedded["5:varint"] = 1_i64 if @features.includes?(Features::Subtitles) + embedded["6:varint"] = 1_i64 if @features.includes?(Features::CCommons) + embedded["7:varint"] = 1_i64 if @features.includes?(Features::ThreeD) + embedded["8:varint"] = 1_i64 if @features.includes?(Features::Live) + embedded["9:varint"] = 1_i64 if @features.includes?(Features::Purchased) + embedded["14:varint"] = 1_i64 if @features.includes?(Features::FourK) + embedded["15:varint"] = 1_i64 if @features.includes?(Features::ThreeSixty) + embedded["23:varint"] = 1_i64 if @features.includes?(Features::Location) + embedded["25:varint"] = 1_i64 if @features.includes?(Features::HDR) + embedded["26:varint"] = 1_i64 if @features.includes?(Features::VR180) + end + + # Initialize an empty protobuf object + object = {} of String => (Int64 | String | Hash(String, Int64)) + + # As usual, everything can be omitted if it has no value + object["2:embedded"] = embedded if !embedded.empty? + + # Default sort is "relevance", so when this option is selected, + # the associated field can be omitted. + if !@sort.relevance? + object["1:varint"] = @sort.to_i64 + end + + # Add page number (if provided) + if page > 1 + object["9:varint"] = ((page - 1) * 20).to_i64 + end + + # If the object is empty, return an empty string, + # otherwise encode to protobuf then to base64 + return "" if object.empty? + + return 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) } + end + + # Function to parse the `sp` URL parameter from Youtube + # search page. It's a base64-encoded protobuf object. + def self.from_yt_params(params : HTTP::Params) : Filters + # Initialize output variable + filters = Filters.new + + # Get parameter, and check emptyness + search_params = params["sp"]? + + if search_params.nil? || search_params.empty? + return filters + end + + # Decode protobuf object + object = search_params + .try { |i| URI.decode_www_form(i) } + .try { |i| Base64.decode(i) } + .try { |i| IO::Memory.new(i) } + .try { |i| Protodec::Any.parse(i) } + + # Parse items from embedded object + if embedded = object["2:0:embedded"]? + # All the following fields (date, type, duration) are optional. + if date = embedded["1:0:varint"]? + filters.date = Date.from_value?(date.as_i) || Date::None + end + + if type = embedded["2:0:varint"]? + filters.type = Type.from_value?(type.as_i) || Type::All + end + + if duration = embedded["3:0:varint"]? + filters.duration = Duration.from_value?(duration.as_i) || Duration::None + end + + # All features should have a value of "1" when enabled, and + # the field should be omitted when the feature is no selected. + features = 0 + features += (embedded["4:0:varint"]?.try &.as_i == 1_i64) ? Features::HD.value : 0 + features += (embedded["5:0:varint"]?.try &.as_i == 1_i64) ? Features::Subtitles.value : 0 + features += (embedded["6:0:varint"]?.try &.as_i == 1_i64) ? Features::CCommons.value : 0 + features += (embedded["7:0:varint"]?.try &.as_i == 1_i64) ? Features::ThreeD.value : 0 + features += (embedded["8:0:varint"]?.try &.as_i == 1_i64) ? Features::Live.value : 0 + features += (embedded["9:0:varint"]?.try &.as_i == 1_i64) ? Features::Purchased.value : 0 + features += (embedded["14:0:varint"]?.try &.as_i == 1_i64) ? Features::FourK.value : 0 + features += (embedded["15:0:varint"]?.try &.as_i == 1_i64) ? Features::ThreeSixty.value : 0 + features += (embedded["23:0:varint"]?.try &.as_i == 1_i64) ? Features::Location.value : 0 + features += (embedded["25:0:varint"]?.try &.as_i == 1_i64) ? Features::HDR.value : 0 + features += (embedded["26:0:varint"]?.try &.as_i == 1_i64) ? Features::VR180.value : 0 + + filters.features = Features.from_value?(features) || Features::None + end + + if sort = object["1:0:varint"]? + filters.sort = Sort.from_value?(sort.as_i) || Sort::Relevance + end + + # Remove URL parameter and return result + params.delete("sp") + return filters + end + end +end diff --git a/src/invidious/search/processors.cr b/src/invidious/search/processors.cr new file mode 100644 index 00000000..d1409c06 --- /dev/null +++ b/src/invidious/search/processors.cr @@ -0,0 +1,64 @@ +module Invidious::Search + module Processors + extend self + + # Regular search (`/search` endpoint) + def regular(query : Query) : Array(SearchItem) + search_params = query.filters.to_yt_params(page: query.page) + + client_config = YoutubeAPI::ClientConfig.new(region: query.region) + initial_data = YoutubeAPI.search(query.text, search_params, client_config: client_config) + + return extract_items(initial_data) + end + + # Search a youtube channel + # TODO: clean code, and rely more on YoutubeAPI + def channel(query : Query) : Array(SearchItem) + response = YT_POOL.client &.get("/channel/#{query.channel}") + + if response.status_code == 404 + response = YT_POOL.client &.get("/user/#{query.channel}") + response = YT_POOL.client &.get("/c/#{query.channel}") if response.status_code == 404 + initial_data = extract_initial_data(response.body) + ucid = initial_data.dig?("header", "c4TabbedHeaderRenderer", "channelId").try(&.as_s?) + raise ChannelSearchException.new(query.channel) if !ucid + else + ucid = query.channel + end + + continuation = produce_channel_search_continuation(ucid, query.text, query.page) + response_json = YoutubeAPI.browse(continuation) + + continuation_items = response_json["onResponseReceivedActions"]? + .try &.[0]["appendContinuationItemsAction"]["continuationItems"] + + return [] of SearchItem if !continuation_items + + items = [] of SearchItem + continuation_items.as_a.select(&.as_h.has_key?("itemSectionRenderer")).each do |item| + extract_item(item["itemSectionRenderer"]["contents"].as_a[0]).try { |t| items << t } + end + + return items + end + + # Search inside of user subscriptions + def subscriptions(query : Query, user : Invidious::User) : Array(ChannelVideo) + view_name = "subscriptions_#{sha256(user.email)}" + + return PG_DB.query_all(" + SELECT id,title,published,updated,ucid,author,length_seconds + FROM ( + SELECT *, + to_tsvector(#{view_name}.title) || + to_tsvector(#{view_name}.author) + as document + FROM #{view_name} + ) v_search WHERE v_search.document @@ plainto_tsquery($1) LIMIT 20 OFFSET $2;", + query.text, (query.page - 1) * 20, + as: ChannelVideo + ) + end + end +end diff --git a/src/invidious/search/query.cr b/src/invidious/search/query.cr new file mode 100644 index 00000000..34b36b1d --- /dev/null +++ b/src/invidious/search/query.cr @@ -0,0 +1,151 @@ +module Invidious::Search + class Query + enum Type + # Types related to YouTube + Regular # Youtube search page + Channel # Youtube channel search box + + # Types specific to Invidious + Subscriptions # Search user subscriptions + Playlist # "Add playlist item" search + end + + getter type : Type = Type::Regular + + @raw_query : String + @query : String = "" + + property filters : Filters = Filters.new + property page : Int32 + property region : String? + property channel : String = "" + + # Return true if @raw_query is either `nil` or empty + private def empty_raw_query? + return @raw_query.empty? + end + + # Same as `empty_raw_query?`, but named for external use + def empty? + return self.empty_raw_query? + end + + # Getter for the query string. + # It is named `text` to reduce confusion (`search_query.text` makes more + # sense than `search_query.query`) + def text + return @query + end + + # Initialize a new search query. + # Parameters are used to get the query string, the page number + # and the search filters (if any). Type tells this function + # where it is being called from (See `Type` above). + def initialize( + params : HTTP::Params, + @type : Type = Type::Regular, + @region : String? = nil + ) + # Get the raw search query string (common to all search types). In + # Regular search mode, also look for the `search_query` URL parameter + if @type.regular? + @raw_query = params["q"]? || params["search_query"]? || "" + else + @raw_query = params["q"]? || "" + end + + # Get the page number (also common to all search types) + @page = params["page"]?.try &.to_i? || 1 + + # Stop here is raw query in empty + # NOTE: maybe raise in the future? + return if self.empty_raw_query? + + # Specific handling + case @type + when .channel? + # In "channel search" mode, filters are ignored, but we still parse + # the query prevent transmission of legacy filters to youtube. + # + _, _, @query, _ = Filters.from_legacy_filters(@raw_query) + # + when .playlist? + # In "add playlist item" mode, filters are parsed from the query + # string itself (legacy), and the channel is ignored. + # + @filters, _, @query, _ = Filters.from_legacy_filters(@raw_query) + # + when .subscriptions?, .regular? + if params["sp"]? + # Parse the `sp` URL parameter (youtube compatibility) + @filters = Filters.from_yt_params(params) + @query = @raw_query || "" + else + # Parse invidious URL parameters (sort, date, etc...) + @filters = Filters.from_iv_params(params) + @channel = params["channel"]? || "" + + if @filters.default? && @raw_query.includes?(':') + # Parse legacy filters from query + @filters, @channel, @query, subs = Filters.from_legacy_filters(@raw_query) + else + @query = @raw_query || "" + end + + if !@channel.empty? + # Switch to channel search mode (filters will be ignored) + @type = Type::Channel + elsif subs + # Switch to subscriptions search mode + @type = Type::Subscriptions + end + end + end + end + + # Run the search query using the corresponding search processor. + # Returns either the results or an empty array of `SearchItem`. + def process(user : Invidious::User? = nil) : Array(SearchItem) | Array(ChannelVideo) + items = [] of SearchItem + + # Don't bother going further if search query is empty + return items if self.empty_raw_query? + + case @type + when .regular?, .playlist? + items = unnest_items(Processors.regular(self)) + # + when .channel? + items = Processors.channel(self) + # + when .subscriptions? + if user + items = Processors.subscriptions(self, user.as(Invidious::User)) + end + end + + return items + end + + # TODO: clean code + private def unnest_items(all_items) : Array(SearchItem) + items = [] of SearchItem + + # Light processing to flatten search results out of Categories. + # They should ideally be supported in the future. + all_items.each do |i| + if i.is_a? Category + i.contents.each do |nest_i| + if !nest_i.is_a? Video + items << nest_i + end + end + else + items << i + end + end + + return items + end + end +end diff --git a/src/invidious/user/cookies.cr b/src/invidious/user/cookies.cr index 99df1b07..65e079ec 100644 --- a/src/invidious/user/cookies.cr +++ b/src/invidious/user/cookies.cr @@ -17,7 +17,8 @@ struct Invidious::User value: sid, expires: Time.utc + 2.years, secure: SECURE, - http_only: true + http_only: true, + samesite: HTTP::Cookie::SameSite::Strict ) end @@ -30,7 +31,8 @@ struct Invidious::User value: URI.encode_www_form(preferences.to_json), expires: Time.utc + 2.years, secure: SECURE, - http_only: true + http_only: false, + samesite: HTTP::Cookie::SameSite::Strict ) end end diff --git a/src/invidious/videos.cr b/src/invidious/videos.cr index 66952c93..4cb049ca 100644 --- a/src/invidious/videos.cr +++ b/src/invidious/videos.cr @@ -374,18 +374,25 @@ struct Video json.array do self.adaptive_fmts.each do |fmt| json.object do - json.field "index", "#{fmt["indexRange"]["start"]}-#{fmt["indexRange"]["end"]}" - json.field "bitrate", fmt["bitrate"].as_i.to_s - json.field "init", "#{fmt["initRange"]["start"]}-#{fmt["initRange"]["end"]}" + # Only available on regular videos, not livestreams/OTF streams + if init_range = fmt["initRange"]? + json.field "init", "#{init_range["start"]}-#{init_range["end"]}" + end + if index_range = fmt["indexRange"]? + json.field "index", "#{index_range["start"]}-#{index_range["end"]}" + end + + # Not available on MPEG-4 Timed Text (`text/mp4`) streams (livestreams only) + json.field "bitrate", fmt["bitrate"].as_i.to_s if fmt["bitrate"]? + json.field "url", fmt["url"] json.field "itag", fmt["itag"].as_i.to_s json.field "type", fmt["mimeType"] - json.field "clen", fmt["contentLength"] + json.field "clen", fmt["contentLength"]? || "-1" json.field "lmt", fmt["lastModified"] json.field "projectionType", fmt["projectionType"] - fmt_info = itag_to_metadata?(fmt["itag"]) - if fmt_info + if fmt_info = itag_to_metadata?(fmt["itag"]) fps = fmt_info["fps"]?.try &.to_i || fmt["fps"]?.try &.as_i || 30 json.field "fps", fps json.field "container", fmt_info["ext"] @@ -405,6 +412,15 @@ struct Video end end end + + # Audio-related data + json.field "audioQuality", fmt["audioQuality"] if fmt.has_key?("audioQuality") + json.field "audioSampleRate", fmt["audioSampleRate"].as_s.to_i if fmt.has_key?("audioSampleRate") + json.field "audioChannels", fmt["audioChannels"] if fmt.has_key?("audioChannels") + + # Extra misc stuff + json.field "colorInfo", fmt["colorInfo"] if fmt.has_key?("colorInfo") + json.field "captionTrack", fmt["captionTrack"] if fmt.has_key?("captionTrack") end end end @@ -585,7 +601,7 @@ struct Video def allowed_regions info - .dig("microformat", "playerMicroformatRenderer", "availableCountries") + .dig?("microformat", "playerMicroformatRenderer", "availableCountries") .try &.as_a.map &.as_s || [] of String end @@ -616,6 +632,7 @@ struct Video fmt["url"] = JSON::Any.new("#{fmt["url"]}&host=#{URI.parse(fmt["url"].as_s).host}") fmt["url"] = JSON::Any.new("#{fmt["url"]}®ion=#{self.info["region"]}") if self.info["region"]? end + fmt_stream.sort_by! { |f| f["width"]?.try &.as_i || 0 } @fmt_stream = fmt_stream return @fmt_stream.as(Array(Hash(String, JSON::Any))) @@ -635,9 +652,7 @@ struct Video fmt["url"] = JSON::Any.new("#{fmt["url"]}&host=#{URI.parse(fmt["url"].as_s).host}") fmt["url"] = JSON::Any.new("#{fmt["url"]}®ion=#{self.info["region"]}") if self.info["region"]? end - # See https://github.com/TeamNewPipe/NewPipe/issues/2415 - # Some streams are segmented by URL `sq/` rather than index, for now we just filter them out - fmt_stream.reject! { |f| !f["indexRange"]? } + fmt_stream.sort_by! { |f| f["width"]?.try &.as_i || 0 } @adaptive_fmts = fmt_stream return @adaptive_fmts.as(Array(Hash(String, JSON::Any))) @@ -887,7 +902,7 @@ def extract_video_info(video_id : String, proxy_region : String? = nil, context_ client_config = YoutubeAPI::ClientConfig.new(proxy_region: proxy_region) if context_screen == "embed" - client_config.client_type = YoutubeAPI::ClientType::WebScreenEmbed + client_config.client_type = YoutubeAPI::ClientType::TvHtml5ScreenEmbed end player_response = YoutubeAPI.player(video_id: video_id, params: "", client_config: client_config) @@ -1108,6 +1123,10 @@ def get_video(id, refresh = true, region = nil, force_refresh = false) end return video +rescue DB::Error + # Avoid common `DB::PoolRetryAttemptsExceeded` error and friends + # Note: All DB errors inherit from `DB::Error` + return fetch_video(id, region) end def fetch_video(id, region) diff --git a/src/invidious/views/add_playlist_items.ecr b/src/invidious/views/add_playlist_items.ecr index ad50909a..22870317 100644 --- a/src/invidious/views/add_playlist_items.ecr +++ b/src/invidious/views/add_playlist_items.ecr @@ -11,7 +11,9 @@ <legend><a href="/playlist?list=<%= playlist.id %>"><%= translate(locale, "Editing playlist `x`", %|"#{HTML.escape(playlist.title)}"|) %></a></legend> <fieldset> - <input class="pure-input-1" type="search" name="q" <% if query %>value="<%= HTML.escape(query) %>"<% else %>placeholder="<%= translate(locale, "Search for videos") %>"<% end %>> + <input class="pure-input-1" type="search" name="q" + <% if query %>value="<%= HTML.escape(query.text) %>"<% end %> + placeholder="<%= translate(locale, "Search for videos") %>"> <input type="hidden" name="list" value="<%= plid %>"> </fieldset> </form> @@ -38,10 +40,11 @@ </div> <% if query %> + <%- query_encoded = URI.encode_www_form(query.text, space_to_plus: true) -%> <div class="pure-g h-box"> <div class="pure-u-1 pure-u-lg-1-5"> - <% if page > 1 %> - <a href="/add_playlist_items?list=<%= plid %>&q=<%= URI.encode_www_form(query.not_nil!) %>&page=<%= page - 1 %>"> + <% if query.page > 1 %> + <a href="/add_playlist_items?list=<%= plid %>&q=<%= query_encoded %>&page=<%= page - 1 %>"> <%= translate(locale, "Previous page") %> </a> <% end %> @@ -49,7 +52,7 @@ <div class="pure-u-1 pure-u-lg-3-5"></div> <div class="pure-u-1 pure-u-lg-1-5" style="text-align:right"> <% if videos.size >= 20 %> - <a href="/add_playlist_items?list=<%= plid %>&q=<%= URI.encode_www_form(query.not_nil!) %>&page=<%= page + 1 %>"> + <a href="/add_playlist_items?list=<%= plid %>&q=<%= query_encoded %>&page=<%= page + 1 %>"> <%= translate(locale, "Next page") %> </a> <% end %> diff --git a/src/invidious/views/components/item.ecr b/src/invidious/views/components/item.ecr index cc4ded74..fb7ad1dc 100644 --- a/src/invidious/views/components/item.ecr +++ b/src/invidious/views/components/item.ecr @@ -52,11 +52,11 @@ <% if !env.get("preferences").as(Preferences).thin_mode %> <div class="thumbnail"> <img loading="lazy" class="thumbnail" src="/vi/<%= item.id %>/mqdefault.jpg"/> - <% if plid = env.get?("remove_playlist_items") %> - <form data-onsubmit="return_false" action="/playlist_ajax?action_remove_video=1&set_video_id=<%= item.index %>&playlist_id=<%= plid %>&referer=<%= env.get("current_page") %>" method="post"> + <% if plid_form = env.get?("remove_playlist_items") %> + <form data-onsubmit="return_false" action="/playlist_ajax?action_remove_video=1&set_video_id=<%= item.index %>&playlist_id=<%= plid_form %>&referer=<%= env.get("current_page") %>" method="post"> <input type="hidden" name="csrf_token" value="<%= HTML.escape(env.get?("csrf_token").try &.as(String) || "") %>"> <p class="watched"> - <a data-onclick="remove_playlist_item" data-index="<%= item.index %>" data-plid="<%= plid %>" href="javascript:void(0)"> + <a data-onclick="remove_playlist_item" data-index="<%= item.index %>" data-plid="<%= plid_form %>" href="javascript:void(0)"> <button type="submit" style="all:unset"> <i class="icon ion-md-trash"></i> </button> @@ -117,11 +117,11 @@ </a> </p> </form> - <% elsif plid = env.get? "add_playlist_items" %> - <form data-onsubmit="return_false" action="/playlist_ajax?action_add_video=1&video_id=<%= item.id %>&playlist_id=<%= plid %>&referer=<%= env.get("current_page") %>" method="post"> + <% elsif plid_form = env.get? "add_playlist_items" %> + <form data-onsubmit="return_false" action="/playlist_ajax?action_add_video=1&video_id=<%= item.id %>&playlist_id=<%= plid_form %>&referer=<%= env.get("current_page") %>" method="post"> <input type="hidden" name="csrf_token" value="<%= HTML.escape(env.get?("csrf_token").try &.as(String) || "") %>"> <p class="watched"> - <a data-onclick="add_playlist_item" data-id="<%= item.id %>" data-plid="<%= plid %>" href="javascript:void(0)"> + <a data-onclick="add_playlist_item" data-id="<%= item.id %>" data-plid="<%= plid_form %>" href="javascript:void(0)"> <button type="submit" style="all:unset"> <i class="icon ion-md-add"></i> </button> diff --git a/src/invidious/views/components/player.ecr b/src/invidious/views/components/player.ecr index 206ba380..fffefc9a 100644 --- a/src/invidious/views/components/player.ecr +++ b/src/invidious/views/components/player.ecr @@ -7,8 +7,19 @@ <source src="<%= URI.parse(hlsvp).request_target %><% if params.local %>?local=true<% end %>" type="application/x-mpegURL" label="livestream"> <% else %> <% if params.listen %> - <% audio_streams.each_with_index do |fmt, i| %> - <source src="/latest_version?id=<%= video.id %>&itag=<%= fmt["itag"] %><% if params.local %>&local=true<% end %>" type='<%= fmt["mimeType"] %>' label="<%= fmt["bitrate"] %>k" selected="<%= i == 0 ? true : false %>"> + <% audio_streams.each_with_index do |fmt, i| + src_url = "/latest_version?id=#{video.id}&itag=#{fmt["itag"]}" + src_url += "&local=true" if params.local + + bitrate = fmt["bitrate"] + mimetype = HTML.escape(fmt["mimeType"].as_s) + + selected = i == 0 ? true : false + %> + <source src="<%= src_url %>" type='<%= mimetype %>' label="<%= bitrate %>k" selected="<%= selected %>"> + <% if !params.local && !CONFIG.disabled?("local") %> + <source src="<%= src_url %>&local=true" type='<%= mimetype %>' hidequalityoption="true"> + <% end %> <% end %> <% else %> <% if params.quality == "dash" %> @@ -28,6 +39,9 @@ selected = params.quality ? (params.quality == quality) : (i == 0) %> <source src="<%= src_url %>" type="<%= mimetype %>" label="<%= quality %>" selected="<%= selected %>"> + <% if !params.local && !CONFIG.disabled?("local") %> + <source src="<%= src_url %>&local=true" type="<%= mimetype %>" hidequalityoption="true"> + <% end %> <% end %> <% end %> diff --git a/src/invidious/views/embed.ecr b/src/invidious/views/embed.ecr index 27a8e266..ce5ff7f0 100644 --- a/src/invidious/views/embed.ecr +++ b/src/invidious/views/embed.ecr @@ -24,7 +24,8 @@ "video_series" => video_series, "params" => params, "preferences" => preferences, - "premiere_timestamp" => video.premiere_timestamp.try &.to_unix + "premiere_timestamp" => video.premiere_timestamp.try &.to_unix, + "local_disabled" => CONFIG.disabled?("local") }.to_pretty_json %> </script> diff --git a/src/invidious/views/search.ecr b/src/invidious/views/search.ecr index 45bbdefc..7110703e 100644 --- a/src/invidious/views/search.ecr +++ b/src/invidious/views/search.ecr @@ -1,147 +1,62 @@ <% content_for "header" do %> -<title><%= search_query.not_nil!.size > 30 ? HTML.escape(query.not_nil![0,30].rstrip(".") + "...") : HTML.escape(query.not_nil!) %> - Invidious</title> +<title><%= query.text.size > 30 ? HTML.escape(query.text[0,30].rstrip(".")) + "…" : HTML.escape(query.text) %> - Invidious</title> +<link rel="stylesheet" href="/css/search.css?v=<%= ASSET_COMMIT %>"> <% end %> -<% search_query_encoded = env.get?("search").try { |x| URI.encode_www_form(x.as(String), space_to_plus: true) } %> +<%- + search_query_encoded = URI.encode_www_form(query.text, space_to_plus: true) + filter_params = query.filters.to_iv_params -<!-- Search redirection and filtering UI --> -<% if videos.size == 0 %> - <h3 style="text-align: center"> - <a href="/redirect?referer=<%= env.get?("current_page") %>"><%= translate(locale, "Broken? Try another Invidious Instance!") %></a> - </h3> -<% else %> - <details id="filters"> - <summary> - <h3 style="display:inline"> <%= translate(locale, "filter") %> </h3> - </summary> - <div id="filters" class="pure-g h-box"> - <div class="pure-u-1-3 pure-u-md-1-5"> - <b><%= translate(locale, "date") %></b> - <hr/> - <% ["hour", "today", "week", "month", "year"].each do |date| %> - <div class="pure-u-1 pure-md-1-5"> - <% if operator_hash.fetch("date", "all") == date %> - <b><%= translate(locale, date) %></b> - <% else %> - <a href="/search?q=<%= URI.encode_www_form(query.not_nil!.gsub(/ ?date:[a-z]+/, "") + " date:" + date) %>&page=<%= page %>"> - <%= translate(locale, date) %> - </a> - <% end %> - </div> - <% end %> - </div> - <div class="pure-u-1-3 pure-u-md-1-5"> - <b><%= translate(locale, "content_type") %></b> - <hr/> - <% ["video", "channel", "playlist", "movie", "show"].each do |content_type| %> - <div class="pure-u-1 pure-md-1-5"> - <% if operator_hash.fetch("content_type", "all") == content_type %> - <b><%= translate(locale, content_type) %></b> - <% else %> - <a href="/search?q=<%= URI.encode_www_form(query.not_nil!.gsub(/ ?content_type:[a-z]+/, "") + " content_type:" + content_type) %>&page=<%= page %>"> - <%= translate(locale, content_type) %> - </a> - <% end %> - </div> - <% end %> - </div> - <div class="pure-u-1-3 pure-u-md-1-5"> - <b><%= translate(locale, "duration") %></b> - <hr/> - <% ["short", "long"].each do |duration| %> - <div class="pure-u-1 pure-md-1-5"> - <% if operator_hash.fetch("duration", "all") == duration %> - <b><%= translate(locale, duration) %></b> - <% else %> - <a href="/search?q=<%= URI.encode_www_form(query.not_nil!.gsub(/ ?duration:[a-z]+/, "") + " duration:" + duration) %>&page=<%= page %>"> - <%= translate(locale, duration) %> - </a> - <% end %> - </div> - <% end %> - </div> - <div class="pure-u-1-3 pure-u-md-1-5"> - <b><%= translate(locale, "features") %></b> - <hr/> - <% ["hd", "subtitles", "creative_commons", "3d", "live", "purchased", "4k", "360", "location", "hdr"].each do |feature| %> - <div class="pure-u-1 pure-md-1-5"> - <% if operator_hash.fetch("features", "all").includes?(feature) %> - <b><%= translate(locale, feature) %></b> - <% elsif operator_hash.has_key?("features") %> - <a href="/search?q=<%= URI.encode_www_form(query.not_nil!.gsub(/features:/, "features:" + feature + ",")) %>&page=<%= page %>"> - <%= translate(locale, feature) %> - </a> - <% else %> - <a href="/search?q=<%= URI.encode_www_form(query.not_nil! + " features:" + feature) %>&page=<%= page %>"> - <%= translate(locale, feature) %> - </a> - <% end %> - </div> - <% end %> - </div> - <div class="pure-u-1-3 pure-u-md-1-5"> - <b><%= translate(locale, "sort") %></b> - <hr/> - <% ["relevance", "rating", "date", "views"].each do |sort| %> - <div class="pure-u-1 pure-md-1-5"> - <% if operator_hash.fetch("sort", "relevance") == sort %> - <b><%= translate(locale, sort) %></b> - <% else %> - <a href="/search?q=<%= URI.encode_www_form(query.not_nil!.gsub(/ ?sort:[a-z]+/, "") + " sort:" + sort) %>&page=<%= page %>"> - <%= translate(locale, sort) %> - </a> - <% end %> - </div> - <% end %> - </div> - </div> - </details> -<% end %> + url_prev_page = "/search?q=#{search_query_encoded}&#{filter_params}&page=#{query.page - 1}" + url_next_page = "/search?q=#{search_query_encoded}&#{filter_params}&page=#{query.page + 1}" -<% if videos.size == 0 %> - <hr style="margin: 0;"/> -<% else %> - <hr/> -<% end %> + redirect_url = Invidious::Frontend::Misc.redirect_url(env) +-%> + +<!-- Search redirection and filtering UI --> +<%= Invidious::Frontend::SearchFilters.generate(query.filters, query.text, query.page, locale) %> +<hr/> <div class="pure-g h-box v-box"> <div class="pure-u-1 pure-u-lg-1-5"> - <% if page > 1 %> - <a href="/search?q=<%= search_query_encoded %>&page=<%= page - 1 %>"> - <%= translate(locale, "Previous page") %> - </a> - <% end %> + <%- if query.page > 1 -%> + <a href="<%= url_prev_page %>"><%= translate(locale, "Previous page") %></a> + <%- end -%> </div> <div class="pure-u-1 pure-u-lg-3-5"></div> <div class="pure-u-1 pure-u-lg-1-5" style="text-align:right"> - <% if videos.size >= 20 %> - <a href="/search?q=<%= search_query_encoded %>&page=<%= page + 1 %>"> - <%= translate(locale, "Next page") %> - </a> - <% end %> + <%- if videos.size >= 20 -%> + <a href="<%= url_next_page %>"><%= translate(locale, "Next page") %></a> + <%- end -%> </div> </div> +<%- if videos.empty? -%> +<div class="h-box no-results-error"> + <div> + <%= translate(locale, "search_message_no_results") %><br/><br/> + <%= translate(locale, "search_message_change_filters_or_query") %><br/><br/> + <%= translate(locale, "search_message_use_another_instance", redirect_url) %> + </div> +</div> +<%- else -%> <div class="pure-g"> - <% videos.each do |item| %> + <%- videos.each do |item| -%> <%= rendered "components/item" %> - <% end %> + <%- end -%> </div> +<%- end -%> <div class="pure-g h-box"> <div class="pure-u-1 pure-u-lg-1-5"> - <% if page > 1 %> - <a href="/search?q=<%= search_query_encoded %>&page=<%= page - 1 %>"> - <%= translate(locale, "Previous page") %> - </a> - <% end %> + <%- if query.page > 1 -%> + <a href="<%= url_prev_page %>"><%= translate(locale, "Previous page") %></a> + <%- end -%> </div> <div class="pure-u-1 pure-u-lg-3-5"></div> <div class="pure-u-1 pure-u-lg-1-5" style="text-align:right"> - <% if videos.size >= 20 %> - <a href="/search?q=<%= search_query_encoded %>&page=<%= page + 1 %>"> - <%= translate(locale, "Next page") %> - </a> - <% end %> + <%- if videos.size >= 20 -%> + <a href="<%= url_next_page %>"><%= translate(locale, "Next page") %></a> + <%- end -%> </div> </div> diff --git a/src/invidious/views/watch.ecr b/src/invidious/views/watch.ecr index 74a5e69f..8b6eb903 100644 --- a/src/invidious/views/watch.ecr +++ b/src/invidious/views/watch.ecr @@ -64,7 +64,8 @@ we're going to need to do it here in order to allow for translations. "preferences" => preferences, "premiere_timestamp" => video.premiere_timestamp.try &.to_unix, "vr" => video.is_vr, - "projection_type" => video.projection_type + "projection_type" => video.projection_type, + "local_disabled" => CONFIG.disabled?("local") }.to_pretty_json %> </script> diff --git a/src/invidious/yt_backend/youtube_api.cr b/src/invidious/yt_backend/youtube_api.cr index 5bbd9213..2678ac6c 100644 --- a/src/invidious/yt_backend/youtube_api.cr +++ b/src/invidious/yt_backend/youtube_api.cr @@ -14,6 +14,7 @@ module YoutubeAPI Android AndroidEmbeddedPlayer AndroidScreenEmbed + TvHtml5ScreenEmbed end # List of hard-coded values used by the different clients @@ -60,6 +61,12 @@ module YoutubeAPI api_key: "AIzaSyAO_FJ2SlqU8Q4STEHLGCilw_Y9_11qcW8", screen: "EMBED", }, + ClientType::TvHtml5ScreenEmbed => { + name: "TVHTML5_SIMPLY_EMBEDDED_PLAYER", + version: "2.0", + api_key: "AIzaSyAO_FJ2SlqU8Q4STEHLGCilw_Y9_11qcW8", + screen: "EMBED", + }, } #################################################################### @@ -401,7 +408,7 @@ module YoutubeAPI client_config ||= DEFAULT_CLIENT_CONFIG # Query parameters - url = "#{endpoint}?key=#{client_config.api_key}" + url = "#{endpoint}?key=#{client_config.api_key}&prettyPrint=false" headers = HTTP::Headers{ "Content-Type" => "application/json; charset=UTF-8", diff --git a/videojs-dependencies.yml b/videojs-dependencies.yml index 6de23d25..e9ccc9dd 100644 --- a/videojs-dependencies.yml +++ b/videojs-dependencies.yml @@ -1,9 +1,7 @@ -# Due to an firefox issue, we're stuck on 7.11.0. If you're hosting a private instance -# and you're using a chromium based browser, feel free to bump this to the latest version -# in order to get support for higher resolutions on more videos. +# Due to a 'video append of' error (see #3011), we're stuck on 7.12.1. video.js: - version: 7.11.0 - shasum: e20747d890716085e7255a90d73c00f32324a224 + version: 7.12.1 + shasum: 1d12eeb1f52e3679e8e4c987d9b9eb37e2247fa2 videojs-contrib-quality-levels: version: 2.1.0 |
