diff --git a/assets/css/default.css b/assets/css/default.css index 910a8fed..ccfe8a0b 100644 --- a/assets/css/default.css +++ b/assets/css/default.css @@ -392,11 +392,19 @@ p.video-data { margin: 0; font-weight: bold; font-size: 80%; } * Comments & community posts */ -#comments { +.comments { max-width: 800px; margin: auto; } +/* + * We don't want the top and bottom margin on the post page. + */ +.comments.post-comments { + margin-bottom: 0; + margin-top: 0; +} + .video-iframe-wrapper { position: relative; height: 0; diff --git a/assets/js/comments.js b/assets/js/comments.js new file mode 100644 index 00000000..35ffa96e --- /dev/null +++ b/assets/js/comments.js @@ -0,0 +1,174 @@ +var video_data = JSON.parse(document.getElementById('video_data').textContent); + +var spinnerHTML = '

'; +var spinnerHTMLwithHR = spinnerHTML + '
'; + +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_comments(event) { + var target = event.target; + var body = target.parentNode.parentNode.parentNode.children[1]; + if (body.style.display === 'none') { + target.textContent = '[ − ]'; + body.style.display = ''; + } else { + target.textContent = '[ + ]'; + body.style.display = 'none'; + } +} + +function hide_youtube_replies(event) { + var target = event.target; + + var sub_text = target.getAttribute('data-inner-text'); + var inner_text = target.getAttribute('data-sub-text'); + + var body = target.parentNode.parentNode.children[1]; + body.style.display = 'none'; + + target.textContent = sub_text; + target.onclick = show_youtube_replies; + target.setAttribute('data-inner-text', inner_text); + target.setAttribute('data-sub-text', sub_text); +} + +function show_youtube_replies(event) { + var target = event.target; + + var sub_text = target.getAttribute('data-inner-text'); + var inner_text = target.getAttribute('data-sub-text'); + + var body = target.parentNode.parentNode.children[1]; + body.style.display = ''; + + target.textContent = sub_text; + target.onclick = hide_youtube_replies; + target.setAttribute('data-inner-text', inner_text); + target.setAttribute('data-sub-text', sub_text); +} + +function get_youtube_comments() { + var comments = document.getElementById('comments'); + + var fallback = comments.innerHTML; + comments.innerHTML = spinnerHTML; + + var baseUrl = video_data.base_url || '/api/v1/comments/'+ video_data.id + var url = baseUrl + + '?format=html' + + '&hl=' + video_data.preferences.locale + + '&thin_mode=' + video_data.preferences.thin_mode; + + if (video_data.ucid) { + url += '&ucid=' + video_data.ucid + } + + var onNon200 = function (xhr) { comments.innerHTML = fallback; }; + if (video_data.params.comments[1] === 'youtube') + onNon200 = function (xhr) {}; + + helpers.xhr('GET', url, {retries: 5, entity_name: 'comments'}, { + on200: function (response) { + var commentInnerHtml = ' \ +
\ +

\ + [ − ] \ + {commentsText} \ +

\ + \ + ' + if (video_data.support_reddit) { + commentInnerHtml += ' \ + {redditComments} \ + \ + ' + } + commentInnerHtml += ' \ +
\ +
{contentHtml}
\ +
' + commentInnerHtml = commentInnerHtml.supplant({ + contentHtml: response.contentHtml, + redditComments: video_data.reddit_comments_text, + commentsText: video_data.comments_text.supplant({ + // toLocaleString correctly splits number with local thousands separator. e.g.: + // '1,234,567.89' for user with English locale + // '1 234 567,89' for user with Russian locale + // '1.234.567,89' for user with Portuguese locale + commentCount: response.commentCount.toLocaleString() + }) + }); + comments.innerHTML = commentInnerHtml; + comments.children[0].children[0].children[0].onclick = toggle_comments; + if (video_data.support_reddit) { + comments.children[0].children[1].children[0].onclick = swap_comments; + } + }, + onNon200: onNon200, // declared above + onError: function (xhr) { + comments.innerHTML = spinnerHTML; + }, + onTimeout: function (xhr) { + comments.innerHTML = spinnerHTML; + } + }); +} + +function get_youtube_replies(target, load_more, load_replies) { + var continuation = target.getAttribute('data-continuation'); + + var body = target.parentNode.parentNode; + var fallback = body.innerHTML; + body.innerHTML = spinnerHTML; + var baseUrl = video_data.base_url || '/api/v1/comments/'+ video_data.id + var url = baseUrl + + '?format=html' + + '&hl=' + video_data.preferences.locale + + '&thin_mode=' + video_data.preferences.thin_mode + + '&continuation=' + continuation; + + if (video_data.ucid) { + url += '&ucid=' + video_data.ucid + } + if (load_replies) url += '&action=action_get_comment_replies'; + + helpers.xhr('GET', url, {}, { + on200: function (response) { + if (load_more) { + body = body.parentNode.parentNode; + body.removeChild(body.lastElementChild); + body.insertAdjacentHTML('beforeend', response.contentHtml); + } else { + body.removeChild(body.lastElementChild); + + var p = document.createElement('p'); + var a = document.createElement('a'); + p.appendChild(a); + + a.href = 'javascript:void(0)'; + a.onclick = hide_youtube_replies; + a.setAttribute('data-sub-text', video_data.hide_replies_text); + a.setAttribute('data-inner-text', video_data.show_replies_text); + a.textContent = video_data.hide_replies_text; + + var div = document.createElement('div'); + div.innerHTML = response.contentHtml; + + body.appendChild(p); + body.appendChild(div); + } + }, + onNon200: function (xhr) { + body.innerHTML = fallback; + }, + onTimeout: function (xhr) { + console.warn('Pulling comments failed'); + body.innerHTML = fallback; + } + }); +} \ No newline at end of file diff --git a/assets/js/post.js b/assets/js/post.js new file mode 100644 index 00000000..fcbc9155 --- /dev/null +++ b/assets/js/post.js @@ -0,0 +1,3 @@ +addEventListener('load', function (e) { + get_youtube_comments(); +}); diff --git a/assets/js/watch.js b/assets/js/watch.js index 92f27bf8..50e1f82e 100644 --- a/assets/js/watch.js +++ b/assets/js/watch.js @@ -1,14 +1,4 @@ 'use strict'; -var video_data = JSON.parse(document.getElementById('video_data').textContent); -var spinnerHTML = '

'; -var spinnerHTMLwithHR = spinnerHTML + '
'; - -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) { var body = target.parentNode.parentNode.children[1]; @@ -21,18 +11,6 @@ function toggle_parent(target) { } } -function toggle_comments(event) { - var target = event.target; - var body = target.parentNode.parentNode.parentNode.children[1]; - if (body.style.display === 'none') { - target.textContent = '[ − ]'; - body.style.display = ''; - } else { - target.textContent = '[ + ]'; - body.style.display = 'none'; - } -} - function swap_comments(event) { var source = event.target.getAttribute('data-comments'); @@ -43,36 +21,6 @@ function swap_comments(event) { } } -function hide_youtube_replies(event) { - var target = event.target; - - var sub_text = target.getAttribute('data-inner-text'); - var inner_text = target.getAttribute('data-sub-text'); - - var body = target.parentNode.parentNode.children[1]; - body.style.display = 'none'; - - target.textContent = sub_text; - target.onclick = show_youtube_replies; - target.setAttribute('data-inner-text', inner_text); - target.setAttribute('data-sub-text', sub_text); -} - -function show_youtube_replies(event) { - var target = event.target; - - var sub_text = target.getAttribute('data-inner-text'); - var inner_text = target.getAttribute('data-sub-text'); - - var body = target.parentNode.parentNode.children[1]; - body.style.display = ''; - - target.textContent = sub_text; - target.onclick = hide_youtube_replies; - target.setAttribute('data-inner-text', inner_text); - target.setAttribute('data-sub-text', sub_text); -} - var continue_button = document.getElementById('continue'); if (continue_button) { continue_button.onclick = continue_autoplay; @@ -261,111 +209,6 @@ function get_reddit_comments() { }); } -function get_youtube_comments() { - var comments = document.getElementById('comments'); - - var fallback = comments.innerHTML; - comments.innerHTML = spinnerHTML; - - var url = '/api/v1/comments/' + video_data.id + - '?format=html' + - '&hl=' + video_data.preferences.locale + - '&thin_mode=' + video_data.preferences.thin_mode; - - var onNon200 = function (xhr) { comments.innerHTML = fallback; }; - if (video_data.params.comments[1] === 'youtube') - onNon200 = function (xhr) {}; - - helpers.xhr('GET', url, {retries: 5, entity_name: 'comments'}, { - on200: function (response) { - comments.innerHTML = ' \ -
\ -

\ - [ − ] \ - {commentsText} \ -

\ - \ - \ - {redditComments} \ - \ - \ -
\ -
{contentHtml}
\ -
'.supplant({ - contentHtml: response.contentHtml, - redditComments: video_data.reddit_comments_text, - commentsText: video_data.comments_text.supplant({ - // toLocaleString correctly splits number with local thousands separator. e.g.: - // '1,234,567.89' for user with English locale - // '1 234 567,89' for user with Russian locale - // '1.234.567,89' for user with Portuguese locale - commentCount: response.commentCount.toLocaleString() - }) - }); - - comments.children[0].children[0].children[0].onclick = toggle_comments; - comments.children[0].children[1].children[0].onclick = swap_comments; - }, - onNon200: onNon200, // declared above - onError: function (xhr) { - comments.innerHTML = spinnerHTML; - }, - onTimeout: function (xhr) { - comments.innerHTML = spinnerHTML; - } - }); -} - -function get_youtube_replies(target, load_more, load_replies) { - var continuation = target.getAttribute('data-continuation'); - - var body = target.parentNode.parentNode; - var fallback = body.innerHTML; - body.innerHTML = spinnerHTML; - - var url = '/api/v1/comments/' + video_data.id + - '?format=html' + - '&hl=' + video_data.preferences.locale + - '&thin_mode=' + video_data.preferences.thin_mode + - '&continuation=' + continuation; - if (load_replies) url += '&action=action_get_comment_replies'; - - helpers.xhr('GET', url, {}, { - on200: function (response) { - if (load_more) { - body = body.parentNode.parentNode; - body.removeChild(body.lastElementChild); - body.insertAdjacentHTML('beforeend', response.contentHtml); - } else { - body.removeChild(body.lastElementChild); - - var p = document.createElement('p'); - var a = document.createElement('a'); - p.appendChild(a); - - a.href = 'javascript:void(0)'; - a.onclick = hide_youtube_replies; - a.setAttribute('data-sub-text', video_data.hide_replies_text); - a.setAttribute('data-inner-text', video_data.show_replies_text); - a.textContent = video_data.hide_replies_text; - - var div = document.createElement('div'); - div.innerHTML = response.contentHtml; - - body.appendChild(p); - body.appendChild(div); - } - }, - onNon200: function (xhr) { - body.innerHTML = fallback; - }, - onTimeout: function (xhr) { - console.warn('Pulling comments failed'); - body.innerHTML = fallback; - } - }); -} - if (video_data.play_next) { player.on('ended', function () { var url = new URL('https://example.com/watch?v=' + video_data.next_video); diff --git a/assets/site.webmanifest b/assets/site.webmanifest index af9432d7..2db6ed9e 100644 --- a/assets/site.webmanifest +++ b/assets/site.webmanifest @@ -15,5 +15,7 @@ ], "theme_color": "#575757", "background_color": "#575757", - "display": "standalone" + "display": "standalone", + "description": "An alternative front-end to YouTube", + "start_url": "/" } diff --git a/locales/ar.json b/locales/ar.json index 877fb9ff..18298913 100644 --- a/locales/ar.json +++ b/locales/ar.json @@ -548,5 +548,11 @@ "generic_button_rss": "RSS", "channel_tab_releases_label": "الإصدارات", "playlist_button_add_items": "إضافة مقاطع فيديو", - "channel_tab_podcasts_label": "البودكاست" + "channel_tab_podcasts_label": "البودكاست", + "generic_channels_count_0": "{{count}} قناة", + "generic_channels_count_1": "{{count}} قناة", + "generic_channels_count_2": "{{count}} قناتان", + "generic_channels_count_3": "{{count}} قنوات", + "generic_channels_count_4": "{{count}} قنوات", + "generic_channels_count_5": "{{count}} قناة" } diff --git a/locales/la.json b/locales/be.json similarity index 100% rename from locales/la.json rename to locales/be.json diff --git a/locales/bg.json b/locales/bg.json new file mode 100644 index 00000000..82591ed8 --- /dev/null +++ b/locales/bg.json @@ -0,0 +1,490 @@ +{ + "Korean (auto-generated)": "Корейски (автоматично генерирано)", + "search_filters_features_option_three_sixty": "360°", + "published - reverse": "публикувани - в обратен ред", + "preferences_quality_dash_option_worst": "Най-ниско качество", + "Password is a required field": "Парола е задължитело поле", + "channel_tab_podcasts_label": "Подкасти", + "Token is expired, please try again": "Токенът е изтекъл, моля опитайте отново", + "Turkish": "Турски", + "preferences_save_player_pos_label": "Запази позицията на плейъра: ", + "View Reddit comments": "Виж Reddit коментари", + "Export data as JSON": "Експортиране на Invidious информацията като JSON", + "About": "За сайта", + "Save preferences": "Запази промените", + "Load more": "Зареди още", + "Import/export": "Импортиране/експортиране", + "Albanian": "Албански", + "New password": "Нова парола", + "Southern Sotho": "Южен Сото", + "channel_tab_videos_label": "Видеа", + "Spanish (Mexico)": "Испански (Мексико)", + "preferences_player_style_label": "Стил на плейъра: ", + "preferences_region_label": "Държавата на съдържанието: ", + "Premieres in `x`": "Премиера в `x`", + "Watch history": "История на гледане", + "generic_subscriptions_count": "{{count}} абонамент", + "generic_subscriptions_count_plural": "{{count}} абонамента", + "preferences_continue_label": "Пускай следващото видео автоматично: ", + "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. Натисни тук за да видиш коментарите, но обърни внимание, че може да отнеме повече време да заредят.", + "Polish": "Полски", + "Icelandic": "Исландски", + "preferences_local_label": "Пускане на видеа през прокси: ", + "Hebrew": "Иврит", + "Fallback captions: ": "Резервни надписи: ", + "search_filters_title": "Филтри", + "search_filters_apply_button": "Приложете избрани филтри", + "Download is disabled": "Изтеглянето е деактивирано", + "User ID is a required field": "Потребителско име е задължително поле", + "comments_points_count": "{{count}} точка", + "comments_points_count_plural": "{{count}} точки", + "next_steps_error_message_go_to_youtube": "Отидеш в YouTube", + "preferences_quality_dash_option_2160p": "2160p", + "search_filters_type_option_video": "Видео", + "Spanish (Latin America)": "Испански (Латинска Америка)", + "Download as: ": "Изтегли като: ", + "Default": "По подразбиране", + "search_filters_sort_option_views": "Гледания", + "search_filters_features_option_four_k": "4K", + "Igbo": "Игбо", + "Subscriptions": "Абонаменти", + "German (auto-generated)": "Немски (автоматично генерирано)", + "`x` is live": "`x` е на живо", + "Azerbaijani": "Азербайджански", + "Premieres `x`": "Премиера `x`", + "Japanese (auto-generated)": "Японски (автоматично генерирано)", + "preferences_quality_option_medium": "Средно", + "footer_donate_page": "Даряване", + "Show replies": "Покажи отговорите", + "Esperanto": "Есперанто", + "search_message_change_filters_or_query": "Опитай да разшириш търсенето си и/или да смениш филтрите.", + "CAPTCHA enabled: ": "Активиране на CAPTCHA: ", + "View playlist on YouTube": "Виж плейлиста в YouTube", + "crash_page_before_reporting": "Преди докладването на бъг, бъди сигурен, че си:", + "Top enabled: ": "Активиране на страница с топ видеа: ", + "preferences_quality_dash_option_best": "Най-високо", + "search_filters_duration_label": "Продължителност", + "Slovak": "Словашки", + "Channel Sponsor": "Канален спонсор", + "generic_videos_count": "{{count}} видео", + "generic_videos_count_plural": "{{count}} видеа", + "videoinfo_started_streaming_x_ago": "Започна да излъчва преди `x`", + "videoinfo_youTube_embed_link": "Вграждане", + "channel_tab_streams_label": "Стриймове", + "oldest": "най-стари", + "playlist_button_add_items": "Добавяне на видеа", + "Import NewPipe data (.zip)": "Импортиране на NewPipe информация (.zip)", + "Clear watch history": "Изчистване на историята на гледане", + "generic_count_minutes": "{{count}} минута", + "generic_count_minutes_plural": "{{count}} минути", + "published": "публикувани", + "Show annotations": "Покажи анотации", + "Login enabled: ": "Активиране на впизване: ", + "Somali": "Сомалийски", + "YouTube comment permalink": "Постоянна връзка на коментарите на YouTube", + "Kurdish": "Кюрдски", + "search_filters_date_option_hour": "Последния час", + "Lao": "Лаоски", + "Maltese": "Малтийски", + "Register": "Регистрация", + "View channel on YouTube": "Виж канала в YouTube", + "Playlist privacy": "Поверителен плейлист", + "preferences_unseen_only_label": "Показвай само негледаните: ", + "Gujarati": "Гуджарати", + "Please log in": "Моля влезте", + "search_filters_sort_option_rating": "Рейтинг", + "Manage subscriptions": "Управление на абонаментите", + "preferences_quality_dash_option_720p": "720p", + "preferences_watch_history_label": "Активирай историята на гледане: ", + "user_saved_playlists": "`x` запази плейлисти", + "preferences_extend_desc_label": "Автоматично разшири описанието на видеото ", + "preferences_max_results_label": "Брой видеа показани на началната страница: ", + "Spanish (Spain)": "Испански (Испания)", + "invidious": "Invidious", + "crash_page_refresh": "пробвал да опресниш страницата", + "Image CAPTCHA": "CAPTCHA с Изображение", + "search_filters_features_option_hd": "HD", + "Chinese (Hong Kong)": "Китайски (Хонг Конг)", + "Import Invidious data": "Импортиране на Invidious JSON информацията", + "Blacklisted regions: ": "Неразрешени региони: ", + "Only show latest video from channel: ": "Показвай само най-новите видеа в канала: ", + "Hmong": "Хмонг", + "French": "Френски", + "search_filters_type_option_channel": "Канал", + "Artist: ": "Артист: ", + "generic_count_months": "{{count}} месец", + "generic_count_months_plural": "{{count}} месеца", + "preferences_annotations_subscribed_label": "Показвай анотаций по подразбиране за абонирани канали? ", + "search_message_use_another_instance": " Можеш също да търсиш на друга инстанция.", + "Danish": "Датски", + "generic_subscribers_count": "{{count}} абонат", + "generic_subscribers_count_plural": "{{count}} абоната", + "Galician": "Галисий", + "newest": "най-нови", + "Empty playlist": "Плейлиста е празен", + "download_subtitles": "Субритри - `x` (.vtt)", + "preferences_category_misc": "Различни предпочитания", + "Uzbek": "Узбекски", + "View JavaScript license information.": "Виж Javascript лиценза.", + "Filipino": "Филипински", + "Malagasy": "Мадагаскарски", + "generic_button_save": "Запиши", + "Dark mode: ": "Тъмен режим: ", + "Public": "Публичен", + "Basque": "Баскски", + "channel:`x`": "Канал:`x`", + "Armenian": "Арменски", + "This channel does not exist.": "Този канал не съществува.", + "Luxembourgish": "Люксембургски", + "preferences_related_videos_label": "Покажи подобни видеа: ", + "English": "Английски", + "Delete account": "Изтриване на акаунт", + "Gaming": "Игри", + "Video mode": "Видео режим", + "preferences_dark_mode_label": "Тема: ", + "crash_page_search_issue": "потърсил за съществуващи проблеми в GitHub", + "preferences_category_subscription": "Предпочитания за абонаменти", + "last": "най-скорощни", + "Chinese (Simplified)": "Китайски (Опростен)", + "Could not create mix.": "Създаването на микс е неуспешно.", + "generic_button_cancel": "Отказ", + "search_filters_type_option_movie": "Филм", + "search_filters_date_option_year": "Тази година", + "Swedish": "Шведски", + "Previous page": "Предишна страница", + "none": "нищо", + "popular": "най-популярни", + "Unsubscribe": "Отписване", + "Slovenian": "Словенски", + "Nepali": "Непалски", + "Time (h:mm:ss):": "Време (h:mm:ss):", + "English (auto-generated)": "Английски (автоматично генерирано)", + "search_filters_sort_label": "Сортирай по", + "View more comments on Reddit": "Виж повече коментари в Reddit", + "Sinhala": "Синхалски", + "preferences_feed_menu_label": "Меню с препоръки: ", + "preferences_autoplay_label": "Автоматично пускане: ", + "Pashto": "Пущунски", + "English (United States)": "Английски (САЩ)", + "Sign In": "Вход", + "subscriptions_unseen_notifs_count": "{{count}} невидяно известие", + "subscriptions_unseen_notifs_count_plural": "{{count}} невидяни известия", + "Log in": "Вход", + "Engagement: ": "Участие: ", + "Album: ": "Албум: ", + "preferences_speed_label": "Скорост по подразбиране: ", + "Import FreeTube subscriptions (.db)": "Импортиране на FreeTube абонаменти (.db)", + "preferences_quality_option_dash": "DASH (адаптивно качество)", + "preferences_show_nick_label": "Показвай потребителското име отгоре: ", + "Private": "Частен", + "Samoan": "Самоански", + "preferences_notifications_only_label": "Показвай само известията (ако има такива): ", + "Create playlist": "Създаване на плейлист", + "next_steps_error_message_refresh": "Опресниш", + "Top": "Топ", + "preferences_quality_dash_option_1080p": "1080p", + "Malayalam": "Малаялам", + "Token": "Токен", + "preferences_comments_label": "Коментари по подразбиране: ", + "Movies": "Филми", + "light": "светла", + "Unlisted": "Скрит", + "preferences_category_admin": "Администраторни предпочитания", + "Erroneous token": "Невалиден токен", + "No": "Не", + "CAPTCHA is a required field": "CAPTCHA е задължително поле", + "Video unavailable": "Неналично видео", + "footer_source_code": "Изходен код", + "New passwords must match": "Новите пароли трябва да съвпадат", + "Playlist does not exist.": "Плейлиста не съществува.", + "Export subscriptions as OPML (for NewPipe & FreeTube)": "Експортиране на абонаментите като OPML (за NewPipe и FreeTube)", + "search_filters_duration_option_short": "Кратко (< 4 минути)", + "search_filters_duration_option_long": "Дълго (> 20 минути)", + "tokens_count": "{{count}} токен", + "tokens_count_plural": "{{count}} токена", + "Yes": "Да", + "Dutch": "Холандски", + "Arabic": "Арабски", + "An alternative front-end to YouTube": "Алтернативен преден план на YouTube", + "View `x` comments": { + "([^.,0-9]|^)1([^.,0-9]|$)": "Виж `x` коментар", + "": "Виж `x` коментари" + }, + "Chinese (China)": "Китайски (Китай)", + "Italian (auto-generated)": "Италиански (автоматично генерирано)", + "alphabetically - reverse": "обратно на азбучния ред", + "channel_tab_shorts_label": "Shorts", + "`x` marked it with a ❤": "`x` го маркира със ❤", + "Current version: ": "Текуща версия: ", + "channel_tab_community_label": "Общност", + "preferences_quality_dash_option_1440p": "1440p", + "preferences_quality_dash_option_360p": "360p", + "`x` uploaded a video": "`x` качи видео", + "Welsh": "Уелски", + "search_message_no_results": "Няма намерени резултати.", + "channel_tab_releases_label": "Версии", + "Bangla": "Бенгалски", + "preferences_quality_dash_option_144p": "144p", + "Indonesian": "Индонезийски", + "`x` ago": "преди `x`", + "Invidious Private Feed for `x`": "Invidious персонални видеа за `x`", + "Finnish": "Финландски", + "Amharic": "Амхарски", + "Malay": "Малайски", + "Interlingue": "Интерлинг", + "search_filters_date_option_month": "Този месец", + "Georgian": "Грузински", + "Xhosa": "Кхоса", + "Marathi": "Маратхи", + "Yoruba": "Йоруба", + "Song: ": "Музика: ", + "Scottish Gaelic": "Шотландски гелски", + "search_filters_features_label": "Функции", + "preferences_quality_label": "Предпочитано качество на видеото: ", + "generic_channels_count": "{{count}} канал", + "generic_channels_count_plural": "{{count}} канала", + "Croatian": "Хърватски", + "Thai": "Тайски", + "Chinese (Taiwan)": "Китайски (Тайван)", + "youtube": "YouTube", + "Source available here.": "Източник наличен тук.", + "LIVE": "На живо", + "Ukrainian": "Украински", + "Russian": "Руски", + "Tajik": "Таджикски", + "Token manager": "Управляване на токени", + "preferences_quality_dash_label": "Предпочитано DASH качество на видеото: ", + "adminprefs_modified_source_code_url_label": "URL до хранилището на променения изходен код", + "Japanese": "Японски", + "Title": "Заглавие", + "Authorize token for `x`?": "Разреши токена за `x`?", + "reddit": "Reddit", + "permalink": "постоянна връзка", + "Trending": "На върха", + "Turkish (auto-generated)": "Турски (автоматично генерирано)", + "Bulgarian": "Български", + "Indonesian (auto-generated)": "Индонезийски (автоматично генерирано)", + "Enable web notifications": "Активирай уеб известия", + "Western Frisian": "Западен фризски", + "search_filters_date_option_week": "Тази седмица", + "Yiddish": "Идиш", + "preferences_category_player": "Предпочитания за плейъра", + "Shared `x` ago": "Споделено преди `x`", + "Swahili": "Суахили", + "Portuguese (auto-generated)": "Португалски (автоматично генерирано)", + "generic_count_years": "{{count}} година", + "generic_count_years_plural": "{{count}} години", + "Wilson score: ": "Wilson оценка: ", + "Genre: ": "Жанр: ", + "videoinfo_invidious_embed_link": "Вграждане на линк", + "Popular enabled: ": "Активиране на популярната страница: ", + "Wrong username or password": "Грешно потребителско име или парола", + "Vietnamese": "Виетнамски", + "alphabetically": "по азбучен ред", + "Afrikaans": "Африкаанс", + "Zulu": "Зулуски", + "(edited)": "(редактирано)", + "Whitelisted regions: ": "Разрешени региони: ", + "Spanish (auto-generated)": "Испански (автоматично генерирано)", + "Could not fetch comments": "Получаването на коментарите е неуспешно", + "Sindhi": "Синдхи", + "News": "Новини", + "preferences_video_loop_label": "Винаги повтаряй: ", + "%A %B %-d, %Y": "%-d %B %Y, %A", + "preferences_quality_option_small": "Ниско", + "English (United Kingdom)": "Английски (Великобритания)", + "Rating: ": "Рейтинг: ", + "channel_tab_playlists_label": "Плейлисти", + "generic_button_edit": "Редактирай", + "Report statistics: ": "Активиране на статистики за репортиране: ", + "Cebuano": "Себуано", + "Chinese (Traditional)": "Китайски (Традиционен)", + "generic_playlists_count": "{{count}} плейлист", + "generic_playlists_count_plural": "{{count}} плейлиста", + "Import NewPipe subscriptions (.json)": "Импортиране на NewPipe абонаменти (.json)", + "Preferences": "Предпочитания", + "Subscribe": "Абониране", + "Import and Export Data": "Импортиране и експортиране на информация", + "preferences_quality_option_hd720": "HD720", + "search_filters_type_option_playlist": "Плейлист", + "Serbian": "Сръбски", + "Kazakh": "Казахски", + "Telugu": "Телугу", + "search_filters_features_option_purchased": "Купено", + "revoke": "отмяна", + "search_filters_sort_option_date": "Дата на качване", + "preferences_category_data": "Предпочитания за информацията", + "search_filters_date_option_none": "Всякаква дата", + "Log out": "Излизане", + "Search": "Търсене", + "preferences_quality_dash_option_auto": "Автоматично", + "dark": "тъмна", + "Cantonese (Hong Kong)": "Кантонски (Хонг Конг)", + "crash_page_report_issue": "Ако никои от горепосочените не помогнаха, моля отворете нов проблем в GitHub (предпочитано на Английски) и добавете следния текст в съобщението (НЕ превеждайте този текст):", + "Czech": "Чешки", + "crash_page_switch_instance": "пробвал да ползваш друга инстанция", + "generic_count_weeks": "{{count}} седмица", + "generic_count_weeks_plural": "{{count}} седмици", + "search_filters_features_option_subtitles": "Субтитри", + "videoinfo_watch_on_youTube": "Виж в YouTube", + "Portuguese": "Португалски", + "Music in this video": "Музика в това видео", + "Hide replies": "Скрий отговорите", + "Password cannot be longer than 55 characters": "Паролата не може да бъде по-дълга от 55 символа", + "footer_modfied_source_code": "Променен изходен код", + "Bosnian": "Босненски", + "Deleted or invalid channel": "Изтрит или невалиден канал", + "Popular": "Популярно", + "search_filters_type_label": "Тип", + "preferences_locale_label": "Език: ", + "Playlists": "Плейлисти", + "generic_button_rss": "RSS", + "Export": "Експортиране", + "preferences_quality_dash_option_4320p": "4320p", + "Erroneous challenge": "Невалиден тест", + "History": "История", + "generic_count_hours": "{{count}} час", + "generic_count_hours_plural": "{{count}} часа", + "Registration enabled: ": "Активиране на регистрация: ", + "Music": "Музика", + "Incorrect password": "Грешна парола", + "Persian": "Перскийски", + "Import": "Импортиране", + "Import/export data": "Импортиране/Експортиране на информация", + "Shared `x`": "Споделено `x`", + "Javanese": "Явански", + "French (auto-generated)": "Френски (автоматично генерирано)", + "Norwegian Bokmål": "Норвежки", + "Catalan": "Каталунски", + "Hindi": "Хинди", + "Tamil": "Тамилски", + "search_filters_features_option_live": "На живо", + "crash_page_read_the_faq": "прочел Често задавани въпроси (FAQ)", + "preferences_default_home_label": "Начална страница по подразбиране: ", + "Download": "Изтегляне", + "Show less": "Покажи по-малко", + "Password": "Парола", + "User ID": "Потребителско име", + "Subscription manager": "Управляване на абонаменти", + "search": "търсене", + "No such user": "Няма такъв потребител", + "View privacy policy.": "Виж политиката за поверителност.", + "Only show latest unwatched video from channel: ": "Показвай само най-новите негледани видеа в канала: ", + "user_created_playlists": "`x` създаде плейлисти", + "Editing playlist `x`": "Редактиране на плейлист `x`", + "preferences_thin_mode_label": "Тънък режим: ", + "E-mail": "Имейл", + "Haitian Creole": "Хаитянски креол", + "Irish": "Ирландски", + "channel_tab_channels_label": "Канали", + "Delete account?": "Изтрий акаунта?", + "Redirect homepage to feed: ": "Препращане на началната страница до препоръки ", + "Urdu": "Урду", + "preferences_vr_mode_label": "Интерактивни 360 градусови видеа (изисква WebGL): ", + "Password cannot be empty": "Паролата не може да бъде празна", + "Mongolian": "Монголски", + "Authorize token?": "Разреши токена?", + "search_filters_type_option_all": "Всякакъв тип", + "Romanian": "Румънски", + "Belarusian": "Беларуски", + "channel name - reverse": "име на канал - в обратен ред", + "Erroneous CAPTCHA": "Невалидна CAPTCHA", + "Watch on YouTube": "Гледай в YouTube", + "search_filters_features_option_location": "Местоположение", + "Could not pull trending pages.": "Получаването на трендинг страниците е неуспешно.", + "German": "Немски", + "search_filters_features_option_c_commons": "Creative Commons", + "Family friendly? ": "За всяка възраст? ", + "Hidden field \"token\" is a required field": "Скритото поле \"токен\" е задължително поле", + "Russian (auto-generated)": "Руски (автоматично генерирано)", + "preferences_quality_dash_option_480p": "480p", + "Corsican": "Корсикански", + "Macedonian": "Македонски", + "comments_view_x_replies": "Виж {{count}} отговор", + "comments_view_x_replies_plural": "Виж {{count}} отговора", + "footer_original_source_code": "Оригинален изходен код", + "Import YouTube subscriptions": "Импортиране на YouTube/OPML абонаменти", + "Lithuanian": "Литовски", + "Nyanja": "Нянджа", + "Updated `x` ago": "Актуализирано преди `x`", + "JavaScript license information": "Информация за Javascript лиценза", + "Spanish": "Испански", + "Latin": "Латински", + "Shona": "Шона", + "Portuguese (Brazil)": "Португалски (Бразилия)", + "Show more": "Покажи още", + "Clear watch history?": "Изчисти историята на търсене?", + "Manage tokens": "Управление на токени", + "Hausa": "Хауса", + "search_filters_features_option_vr180": "VR180", + "preferences_category_visual": "Визуални предпочитания", + "Italian": "Италиански", + "preferences_volume_label": "Сила на звука на плейъра: ", + "error_video_not_in_playlist": "Заявеното видео не съществува в този плейлист. Натиснете тук за началната страница на плейлиста.", + "preferences_listen_label": "Само звук по подразбиране: ", + "Dutch (auto-generated)": "Холандски (автоматично генерирано)", + "preferences_captions_label": "Надписи по подразбиране: ", + "generic_count_days": "{{count}} ден", + "generic_count_days_plural": "{{count}} дни", + "Hawaiian": "Хавайски", + "Could not get channel info.": "Получаването на информация за канала е неуспешно.", + "View as playlist": "Виж като плейлист", + "Vietnamese (auto-generated)": "Виетнамски (автоматично генерирано)", + "search_filters_duration_option_none": "Всякаква продължителност", + "preferences_quality_dash_option_240p": "240p", + "Latvian": "Латвийски", + "search_filters_features_option_hdr": "HDR", + "preferences_sort_label": "Сортирай видеата по: ", + "Estonian": "Естонски", + "Hidden field \"challenge\" is a required field": "Скритото поле \"тест\" е задължително поле", + "footer_documentation": "Документация", + "Kyrgyz": "Киргизски", + "preferences_continue_autoplay_label": "Пускай следващотото видео автоматично: ", + "Chinese": "Китайски", + "search_filters_sort_option_relevance": "Уместност", + "source": "източник", + "Fallback comments: ": "Резервни коментари: ", + "preferences_automatic_instance_redirect_label": "Автоматично препращане на инстанция (чрез redirect.invidious.io): ", + "Maori": "Маори", + "generic_button_delete": "Изтрий", + "Import YouTube playlist (.csv)": "Импортиране на YouTube плейлист (.csv)", + "Switch Invidious Instance": "Смени Invidious инстанция", + "channel name": "име на канал", + "Audio mode": "Аудио режим", + "search_filters_type_option_show": "Сериал", + "search_filters_date_option_today": "Днес", + "search_filters_features_option_three_d": "3D", + "next_steps_error_message": "След което можеш да пробваш да: ", + "Hide annotations": "Скрий анотации", + "Standard YouTube license": "Стандартен YouTube лиценз", + "Text CAPTCHA": "Текст CAPTCHA", + "Log in/register": "Вход/регистрация", + "Punjabi": "Пенджаби", + "Change password": "Смяна на паролата", + "License: ": "Лиценз: ", + "search_filters_duration_option_medium": "Средно (4 - 20 минути)", + "Delete playlist": "Изтриване на плейлист", + "Delete playlist `x`?": "Изтрий плейлиста `x`?", + "Korean": "Корейски", + "Export subscriptions as OPML": "Експортиране на абонаментите като OPML", + "unsubscribe": "отписване", + "View YouTube comments": "Виж YouTube коментарите", + "Kannada": "Каннада", + "Not a playlist.": "Невалиден плейлист.", + "Wrong answer": "Грешен отговор", + "Released under the AGPLv3 on Github.": "Публикувано под AGPLv3 в GitHub.", + "Burmese": "Бирмански", + "Sundanese": "Сундански", + "Hungarian": "Унгарски", + "generic_count_seconds": "{{count}} секунда", + "generic_count_seconds_plural": "{{count}} секунди", + "search_filters_date_label": "Дата на качване", + "Greek": "Гръцки", + "crash_page_you_found_a_bug": "Изглежда намери бъг в Invidious!", + "View all playlists": "Виж всички плейлисти", + "Khmer": "Кхмерски", + "preferences_annotations_label": "Покажи анотаций по подразбиране: ", + "generic_views_count": "{{count}} гледане", + "generic_views_count_plural": "{{count}} гледания", + "Next page": "Следваща страница" +} diff --git a/locales/ca.json b/locales/ca.json index 4392c2a9..a718eb2b 100644 --- a/locales/ca.json +++ b/locales/ca.json @@ -476,5 +476,15 @@ "Redirect homepage to feed: ": "Redirigeix la pàgina d'inici al feed: ", "Standard YouTube license": "Llicència estàndard de YouTube", "Download is disabled": "Les baixades s'han inhabilitat", - "Import YouTube playlist (.csv)": "Importar llista de reproducció de YouTube (.csv)" + "Import YouTube playlist (.csv)": "Importar llista de reproducció de YouTube (.csv)", + "channel_tab_podcasts_label": "Podcasts", + "playlist_button_add_items": "Afegeix vídeos", + "generic_button_save": "Desa", + "generic_button_cancel": "Cancel·la", + "channel_tab_releases_label": "Publicacions", + "generic_channels_count": "{{count}} canal", + "generic_channels_count_plural": "{{count}} canals", + "generic_button_edit": "Edita", + "generic_button_rss": "RSS", + "generic_button_delete": "Suprimeix" } diff --git a/locales/cs.json b/locales/cs.json index b2cce0bd..10c114eb 100644 --- a/locales/cs.json +++ b/locales/cs.json @@ -500,5 +500,8 @@ "channel_tab_releases_label": "Vydání", "generic_button_edit": "Upravit", "generic_button_rss": "RSS", - "playlist_button_add_items": "Přidat videa" + "playlist_button_add_items": "Přidat videa", + "generic_channels_count_0": "{{count}} kanál", + "generic_channels_count_1": "{{count}} kanály", + "generic_channels_count_2": "{{count}} kanálů" } diff --git a/locales/de.json b/locales/de.json index 6ceaa44b..59c6a49c 100644 --- a/locales/de.json +++ b/locales/de.json @@ -97,7 +97,7 @@ "Change password": "Passwort ändern", "Manage subscriptions": "Abonnements verwalten", "Manage tokens": "Tokens verwalten", - "Watch history": "Verlauf", + "Watch history": "Wiedergabeverlauf", "Delete account": "Account löschen", "preferences_category_admin": "Administrator-Einstellungen", "preferences_default_home_label": "Standard-Startseite: ", @@ -476,11 +476,15 @@ "Standard YouTube license": "Standard YouTube-Lizenz", "Song: ": "Musik: ", "Download is disabled": "Herunterladen ist deaktiviert", - "Import YouTube playlist (.csv)": "YouTube Playlist Importieren (.csv)", + "Import YouTube playlist (.csv)": "YouTube Wiedergabeliste importieren (.csv)", "generic_button_delete": "Löschen", "generic_button_edit": "Bearbeiten", "generic_button_save": "Speichern", "generic_button_cancel": "Abbrechen", "generic_button_rss": "RSS", - "playlist_button_add_items": "Videos hinzufügen" + "playlist_button_add_items": "Videos hinzufügen", + "channel_tab_podcasts_label": "Podcasts", + "channel_tab_releases_label": "Veröffentlichungen", + "generic_channels_count": "{{count}} Kanal", + "generic_channels_count_plural": "{{count}} Kanäle" } diff --git a/locales/el.json b/locales/el.json index 13cff649..1d827eba 100644 --- a/locales/el.json +++ b/locales/el.json @@ -41,7 +41,7 @@ "Time (h:mm:ss):": "Ώρα (ω:λλ:δδ):", "Text CAPTCHA": "Κείμενο CAPTCHA", "Image CAPTCHA": "Εικόνα CAPTCHA", - "Sign In": "Σύνδεση", + "Sign In": "Εγγραφή", "Register": "Εγγραφή", "E-mail": "Ηλεκτρονικό ταχυδρομείο", "Preferences": "Προτιμήσεις", @@ -145,7 +145,7 @@ "View YouTube comments": "Προβολή σχολίων από το YouTube", "View more comments on Reddit": "Προβολή περισσότερων σχολίων στο Reddit", "View `x` comments": { - "([^.,0-9]|^)1([^.,0-9]|$)": "Προβολή `x` σχολίων", + "([^.,0-9]|^)1([^.,0-9]|$)": "Προβολή `x` σχολίου", "": "Προβολή `x` σχολίων" }, "View Reddit comments": "Προβολή σχολίων από το Reddit", @@ -349,7 +349,7 @@ "crash_page_you_found_a_bug": "Φαίνεται ότι βρήκατε ένα σφάλμα στο Invidious!", "crash_page_before_reporting": "Πριν αναφέρετε ένα σφάλμα, βεβαιωθείτε ότι έχετε:", "crash_page_refresh": "προσπαθήσει να ανανεώσετε τη σελίδα", - "crash_page_read_the_faq": "διαβάσει τις Συχνές Ερωτήσεις (ΣΕ)", + "crash_page_read_the_faq": "διαβάστε τις Συχνές Ερωτήσεις (ΣΕ)", "crash_page_search_issue": "αναζητήσει για υπάρχοντα θέματα στο GitHub", "generic_views_count": "{{count}} προβολή", "generic_views_count_plural": "{{count}} προβολές", @@ -442,5 +442,49 @@ "search_filters_type_option_show": "Μπάρα προόδου διαβάσματος", "preferences_watch_history_label": "Ενεργοποίηση ιστορικού παρακολούθησης: ", "search_filters_title": "Φίλτρο", - "search_message_no_results": "Δε βρέθηκαν αποτελέσματα." + "search_message_no_results": "Δε βρέθηκαν αποτελέσματα.", + "channel_tab_podcasts_label": "Podcast", + "preferences_save_player_pos_label": "Αποθήκευση σημείου αναπαραγωγής: ", + "search_filters_apply_button": "Εφαρμογή επιλεγμένων φίλτρων", + "Download is disabled": "Είναι απενεργοποιημένη η λήψη", + "comments_points_count": "{{count}} βαθμός", + "comments_points_count_plural": "{{count}} βαθμοί", + "search_filters_sort_option_views": "Προβολές", + "search_message_change_filters_or_query": "Προσπαθήστε να διευρύνετε το ερώτημα αναζήτησης ή/και να αλλάξετε τα φίλτρα.", + "Channel Sponsor": "Χορηγός Καναλιού", + "channel_tab_streams_label": "Ζωντανή μετάδοση", + "playlist_button_add_items": "Προσθήκη βίντεο", + "Artist: ": "Καλλιτέχνης: ", + "search_message_use_another_instance": " Μπορείτε επίσης να αναζητήσετε σε άλλο instance.", + "generic_button_save": "Αποθήκευση", + "generic_button_cancel": "Ακύρωση", + "subscriptions_unseen_notifs_count": "{{count}} μη αναγνωσμένη ειδοποίηση", + "subscriptions_unseen_notifs_count_plural": "{{count}} μη αναγνωσμένες ειδοποιήσεις", + "Album: ": "Δίσκος: ", + "tokens_count": "{{count}} σύμβολο", + "tokens_count_plural": "{{count}} σύμβολα", + "channel_tab_shorts_label": "Short", + "channel_tab_releases_label": "Κυκλοφορίες", + "Song: ": "Τραγούδι: ", + "generic_channels_count": "{{count}} κανάλι", + "generic_channels_count_plural": "{{count}} κανάλια", + "Popular enabled: ": "Ενεργοποιημένα Δημοφιλή: ", + "channel_tab_playlists_label": "Λίστες αναπαραγωγής", + "generic_button_edit": "Επεξεργασία", + "search_filters_date_option_none": "Οποιαδήποτε ημερομηνία", + "crash_page_switch_instance": "προσπάθεια χρήσης άλλου instance", + "Music in this video": "Μουσική σε αυτό το βίντεο", + "generic_button_rss": "RSS", + "channel_tab_channels_label": "Κανάλια", + "search_filters_type_option_all": "Οποιοσδήποτε τύπος", + "search_filters_features_option_vr180": "VR180", + "error_video_not_in_playlist": "Το αιτούμενο βίντεο δεν υπάρχει στη δεδομένη λίστα αναπαραγωγής. Πατήστε εδώ για επιστροφή στη κεντρική σελίδα λιστών αναπαραγωγής.", + "search_filters_duration_option_none": "Οποιαδήποτε διάρκεια", + "preferences_automatic_instance_redirect_label": "Αυτόματη ανακατεύθυνση instance (εναλλακτική σε redirect.invidious.io): ", + "generic_button_delete": "Διαγραφή", + "Import YouTube playlist (.csv)": "Εισαγωγή λίστας αναπαραγωγής YouTube (.csv)", + "Switch Invidious Instance": "Αλλαγή Instance Invidious", + "Standard YouTube license": "Τυπική άδεια YouTube", + "search_filters_duration_option_medium": "Μεσαία (4 - 20 λεπτά)", + "search_filters_date_label": "Ημερομηνία αναφόρτωσης" } diff --git a/locales/eo.json b/locales/eo.json index 6d1b0bc1..7276c890 100644 --- a/locales/eo.json +++ b/locales/eo.json @@ -484,5 +484,7 @@ "channel_tab_podcasts_label": "Podkastoj", "generic_button_cancel": "Nuligi", "channel_tab_releases_label": "Eldonoj", - "generic_button_save": "Konservi" + "generic_button_save": "Konservi", + "generic_channels_count": "{{count}} kanalo", + "generic_channels_count_plural": "{{count}} kanaloj" } diff --git a/locales/es.json b/locales/es.json index b4a56030..0b8463ea 100644 --- a/locales/es.json +++ b/locales/es.json @@ -484,5 +484,7 @@ "generic_button_cancel": "Cancelar", "generic_button_rss": "RSS", "channel_tab_podcasts_label": "Podcasts", - "channel_tab_releases_label": "Publicaciones" + "channel_tab_releases_label": "Publicaciones", + "generic_channels_count": "{{count}} canal", + "generic_channels_count_plural": "{{count}} canales" } diff --git a/locales/fr.json b/locales/fr.json index 7fea8f14..772c81c8 100644 --- a/locales/fr.json +++ b/locales/fr.json @@ -1,16 +1,22 @@ { - "generic_channels_count": "{{count}} chaîne", - "generic_channels_count_plural": "{{count}} chaînes", - "generic_views_count": "{{count}} vue", - "generic_views_count_plural": "{{count}} vues", - "generic_videos_count": "{{count}} vidéo", - "generic_videos_count_plural": "{{count}} vidéos", - "generic_playlists_count": "{{count}} liste de lecture", - "generic_playlists_count_plural": "{{count}} listes de lecture", - "generic_subscribers_count": "{{count}} abonné", - "generic_subscribers_count_plural": "{{count}} abonnés", - "generic_subscriptions_count": "{{count}} abonnement", - "generic_subscriptions_count_plural": "{{count}} abonnements", + "generic_channels_count_0": "{{count}} chaîne", + "generic_channels_count_1": "{{count}} de chaînes", + "generic_channels_count_2": "{{count}} chaînes", + "generic_views_count_0": "{{count}} vue", + "generic_views_count_1": "{{count}} de vues", + "generic_views_count_2": "{{count}} vues", + "generic_videos_count_0": "{{count}} vidéo", + "generic_videos_count_1": "{{count}} de vidéos", + "generic_videos_count_2": "{{count}} vidéos", + "generic_playlists_count_0": "{{count}} liste de lecture", + "generic_playlists_count_1": "{{count}} listes de lecture", + "generic_playlists_count_2": "{{count}} listes de lecture", + "generic_subscribers_count_0": "{{count}} abonné", + "generic_subscribers_count_1": "{{count}} d'abonnés", + "generic_subscribers_count_2": "{{count}} abonnés", + "generic_subscriptions_count_0": "{{count}} abonnement", + "generic_subscriptions_count_1": "{{count}} d'abonnements", + "generic_subscriptions_count_2": "{{count}} abonnements", "generic_button_delete": "Supprimer", "generic_button_edit": "Editer", "generic_button_save": "Enregistrer", @@ -130,14 +136,16 @@ "Subscription manager": "Gestionnaire d'abonnement", "Token manager": "Gestionnaire de token", "Token": "Token", - "tokens_count": "{{count}} jeton", - "tokens_count_plural": "{{count}} jetons", + "tokens_count_0": "{{count}} jeton", + "tokens_count_1": "{{count}} de jetons", + "tokens_count_2": "{{count}} jetons", "Import/export": "Importer/Exporter", "unsubscribe": "se désabonner", "revoke": "révoquer", "Subscriptions": "Abonnements", - "subscriptions_unseen_notifs_count": "{{count}} notification non vue", - "subscriptions_unseen_notifs_count_plural": "{{count}} notifications non vues", + "subscriptions_unseen_notifs_count_0": "{{count}} notification non vue", + "subscriptions_unseen_notifs_count_1": "{{count}} de notifications non vues", + "subscriptions_unseen_notifs_count_2": "{{count}} notifications non vues", "search": "rechercher", "Log out": "Se déconnecter", "Released under the AGPLv3 on Github.": "Publié sous licence AGPLv3 sur GitHub.", @@ -199,12 +207,14 @@ "This channel does not exist.": "Cette chaine n'existe pas.", "Could not get channel info.": "Impossible de charger les informations de cette chaîne.", "Could not fetch comments": "Impossible de charger les commentaires", - "comments_view_x_replies": "Voir {{count}} réponse", - "comments_view_x_replies_plural": "Voir {{count}} réponses", + "comments_view_x_replies_0": "Voir {{count}} réponse", + "comments_view_x_replies_1": "Voir {{count}} de réponses", + "comments_view_x_replies_2": "Voir {{count}} réponses", "`x` ago": "il y a `x`", "Load more": "Voir plus", - "comments_points_count": "{{count}} point", - "comments_points_count_plural": "{{count}} points", + "comments_points_count_0": "{{count}} point", + "comments_points_count_1": "{{count}} de points", + "comments_points_count_2": "{{count}} points", "Could not create mix.": "Impossible de charger cette liste de lecture.", "Empty playlist": "La liste de lecture est vide", "Not a playlist.": "La liste de lecture est invalide.", @@ -322,20 +332,27 @@ "Yiddish": "Yiddish", "Yoruba": "Yoruba", "Zulu": "Zoulou", - "generic_count_years": "{{count}} an", - "generic_count_years_plural": "{{count}} ans", - "generic_count_months": "{{count}} mois", - "generic_count_months_plural": "{{count}} mois", - "generic_count_weeks": "{{count}} semaine", - "generic_count_weeks_plural": "{{count}} semaines", - "generic_count_days": "{{count}} jour", - "generic_count_days_plural": "{{count}} jours", - "generic_count_hours": "{{count}} heure", - "generic_count_hours_plural": "{{count}} heures", - "generic_count_minutes": "{{count}} minute", - "generic_count_minutes_plural": "{{count}} minutes", - "generic_count_seconds": "{{count}} seconde", - "generic_count_seconds_plural": "{{count}} secondes", + "generic_count_years_0": "{{count}} an", + "generic_count_years_1": "{{count}} ans", + "generic_count_years_2": "{{count}} ans", + "generic_count_months_0": "{{count}} mois", + "generic_count_months_1": "{{count}} mois", + "generic_count_months_2": "{{count}} mois", + "generic_count_weeks_0": "{{count}} semaine", + "generic_count_weeks_1": "{{count}} semaines", + "generic_count_weeks_2": "{{count}} semaines", + "generic_count_days_0": "{{count}} jour", + "generic_count_days_1": "{{count}} jours", + "generic_count_days_2": "{{count}} jours", + "generic_count_hours_0": "{{count}} heure", + "generic_count_hours_1": "{{count}} heures", + "generic_count_hours_2": "{{count}} heures", + "generic_count_minutes_0": "{{count}} minute", + "generic_count_minutes_1": "{{count}} minutes", + "generic_count_minutes_2": "{{count}} minutes", + "generic_count_seconds_0": "{{count}} seconde", + "generic_count_seconds_1": "{{count}} secondes", + "generic_count_seconds_2": "{{count}} secondes", "Fallback comments: ": "Commentaires alternatifs : ", "Popular": "Populaire", "Search": "Rechercher", diff --git a/locales/hr.json b/locales/hr.json index ba3dd5e5..ef931202 100644 --- a/locales/hr.json +++ b/locales/hr.json @@ -500,5 +500,8 @@ "generic_button_save": "Spremi", "generic_button_cancel": "Odustani", "generic_button_rss": "RSS", - "channel_tab_releases_label": "Izdanja" + "channel_tab_releases_label": "Izdanja", + "generic_channels_count_0": "{{count}} kanal", + "generic_channels_count_1": "{{count}} kanala", + "generic_channels_count_2": "{{count}} kanala" } diff --git a/locales/id.json b/locales/id.json index ef677251..8961880b 100644 --- a/locales/id.json +++ b/locales/id.json @@ -446,5 +446,28 @@ "crash_page_read_the_faq": "baca Soal Sering Ditanya (SSD/FAQ)", "crash_page_search_issue": "mencari isu yang ada di GitHub", "crash_page_report_issue": "Jika yang di atas tidak membantu, buka isu baru di GitHub (sebaiknya dalam bahasa Inggris) dan sertakan teks berikut dalam pesan Anda (JANGAN terjemahkan teks tersebut):", - "Popular enabled: ": "Populer diaktifkan: " + "Popular enabled: ": "Populer diaktifkan: ", + "channel_tab_podcasts_label": "Podcast", + "Download is disabled": "Download dinonaktifkan", + "Channel Sponsor": "Saluran Sponsor", + "channel_tab_streams_label": "Streaming langsung", + "playlist_button_add_items": "Tambahkan video", + "Artist: ": "Artis: ", + "generic_button_save": "Simpan", + "generic_button_cancel": "Batal", + "Album: ": "Album: ", + "channel_tab_shorts_label": "Shorts", + "channel_tab_releases_label": "Terbit", + "Interlingue": "Interlingue", + "Song: ": "Lagu: ", + "generic_channels_count_0": "Saluran {{count}}", + "channel_tab_playlists_label": "Daftar putar", + "generic_button_edit": "Ubah", + "Music in this video": "Musik dalam video ini", + "generic_button_rss": "RSS", + "channel_tab_channels_label": "Saluran", + "error_video_not_in_playlist": "Video yang diminta tidak ada dalam daftar putar ini. Klik di sini untuk halaman beranda daftar putar.", + "generic_button_delete": "Hapus", + "Import YouTube playlist (.csv)": "Impor daftar putar YouTube (.csv)", + "Standard YouTube license": "Lisensi YouTube standar" } diff --git a/locales/it.json b/locales/it.json index 894eb97f..7e1b12c6 100644 --- a/locales/it.json +++ b/locales/it.json @@ -1,10 +1,13 @@ { - "generic_subscribers_count": "{{count}} iscritto", - "generic_subscribers_count_plural": "{{count}} iscritti", - "generic_videos_count": "{{count}} video", - "generic_videos_count_plural": "{{count}} video", - "generic_playlists_count": "{{count}} playlist", - "generic_playlists_count_plural": "{{count}} playlist", + "generic_subscribers_count_0": "{{count}} iscritto", + "generic_subscribers_count_1": "{{count}} iscritti", + "generic_subscribers_count_2": "{{count}} iscritti", + "generic_videos_count_0": "{{count}} video", + "generic_videos_count_1": "{{count}} video", + "generic_videos_count_2": "{{count}} video", + "generic_playlists_count_0": "{{count}} playlist", + "generic_playlists_count_1": "{{count}} playlist", + "generic_playlists_count_2": "{{count}} playlist", "LIVE": "IN DIRETTA", "Shared `x` ago": "Condiviso `x` fa", "Unsubscribe": "Disiscriviti", @@ -113,16 +116,19 @@ "Subscription manager": "Gestione delle iscrizioni", "Token manager": "Gestione dei gettoni", "Token": "Gettone", - "generic_subscriptions_count": "{{count}} iscrizione", - "generic_subscriptions_count_plural": "{{count}} iscrizioni", - "tokens_count": "{{count}} gettone", - "tokens_count_plural": "{{count}} gettoni", + "generic_subscriptions_count_0": "{{count}} iscrizione", + "generic_subscriptions_count_1": "{{count}} iscrizioni", + "generic_subscriptions_count_2": "{{count}} iscrizioni", + "tokens_count_0": "{{count}} gettone", + "tokens_count_1": "{{count}} gettoni", + "tokens_count_2": "{{count}} gettoni", "Import/export": "Importa/esporta", "unsubscribe": "disiscriviti", "revoke": "revoca", "Subscriptions": "Iscrizioni", - "subscriptions_unseen_notifs_count": "{{count}} notifica non visualizzata", - "subscriptions_unseen_notifs_count_plural": "{{count}} notifiche non visualizzate", + "subscriptions_unseen_notifs_count_0": "{{count}} notifica non visualizzata", + "subscriptions_unseen_notifs_count_1": "{{count}} notifiche non visualizzate", + "subscriptions_unseen_notifs_count_2": "{{count}} notifiche non visualizzate", "search": "Cerca", "Log out": "Esci", "Source available here.": "Codice sorgente.", @@ -151,8 +157,9 @@ "Whitelisted regions: ": "Regioni in lista bianca: ", "Blacklisted regions: ": "Regioni in lista nera: ", "Shared `x`": "Condiviso `x`", - "generic_views_count": "{{count}} visualizzazione", - "generic_views_count_plural": "{{count}} visualizzazioni", + "generic_views_count_0": "{{count}} visualizzazione", + "generic_views_count_1": "{{count}} visualizzazioni", + "generic_views_count_2": "{{count}} visualizzazioni", "Premieres in `x`": "In anteprima in `x`", "Premieres `x`": "In anteprima `x`", "Hi! Looks like you have JavaScript turned off. Click here to view comments, keep in mind they may take a bit longer to load.": "Ciao, Sembra che tu abbia disattivato JavaScript. Clicca qui per visualizzare i commenti, ma considera che il caricamento potrebbe richiedere più tempo.", @@ -300,20 +307,27 @@ "Yiddish": "Yiddish", "Yoruba": "Yoruba", "Zulu": "Zulu", - "generic_count_years": "{{count}} anno", - "generic_count_years_plural": "{{count}} anni", - "generic_count_months": "{{count}} mese", - "generic_count_months_plural": "{{count}} mesi", - "generic_count_weeks": "{{count}} settimana", - "generic_count_weeks_plural": "{{count}} settimane", - "generic_count_days": "{{count}} giorno", - "generic_count_days_plural": "{{count}} giorni", - "generic_count_hours": "{{count}} ora", - "generic_count_hours_plural": "{{count}} ore", - "generic_count_minutes": "{{count}} minuto", - "generic_count_minutes_plural": "{{count}} minuti", - "generic_count_seconds": "{{count}} secondo", - "generic_count_seconds_plural": "{{count}} secondi", + "generic_count_years_0": "{{count}} anno", + "generic_count_years_1": "{{count}} anni", + "generic_count_years_2": "{{count}} anni", + "generic_count_months_0": "{{count}} mese", + "generic_count_months_1": "{{count}} mesi", + "generic_count_months_2": "{{count}} mesi", + "generic_count_weeks_0": "{{count}} settimana", + "generic_count_weeks_1": "{{count}} settimane", + "generic_count_weeks_2": "{{count}} settimane", + "generic_count_days_0": "{{count}} giorno", + "generic_count_days_1": "{{count}} giorni", + "generic_count_days_2": "{{count}} giorni", + "generic_count_hours_0": "{{count}} ora", + "generic_count_hours_1": "{{count}} ore", + "generic_count_hours_2": "{{count}} ore", + "generic_count_minutes_0": "{{count}} minuto", + "generic_count_minutes_1": "{{count}} minuti", + "generic_count_minutes_2": "{{count}} minuti", + "generic_count_seconds_0": "{{count}} secondo", + "generic_count_seconds_1": "{{count}} secondi", + "generic_count_seconds_2": "{{count}} secondi", "Fallback comments: ": "Commenti alternativi: ", "Popular": "Popolare", "Search": "Cerca", @@ -417,10 +431,12 @@ "search_filters_duration_option_short": "Corto (< 4 minuti)", "search_filters_duration_option_long": "Lungo (> 20 minuti)", "search_filters_features_option_purchased": "Acquistato", - "comments_view_x_replies": "Vedi {{count}} risposta", - "comments_view_x_replies_plural": "Vedi {{count}} risposte", - "comments_points_count": "{{count}} punto", - "comments_points_count_plural": "{{count}} punti", + "comments_view_x_replies_0": "Vedi {{count}} risposta", + "comments_view_x_replies_1": "Vedi {{count}} risposte", + "comments_view_x_replies_2": "Vedi {{count}} risposte", + "comments_points_count_0": "{{count}} punto", + "comments_points_count_1": "{{count}} punti", + "comments_points_count_2": "{{count}} punti", "Portuguese (auto-generated)": "Portoghese (generati automaticamente)", "crash_page_you_found_a_bug": "Sembra che tu abbia trovato un bug in Invidious!", "crash_page_switch_instance": "provato a usare un'altra istanza", @@ -484,5 +500,8 @@ "generic_button_delete": "Elimina", "generic_button_save": "Salva", "playlist_button_add_items": "Aggiungi video", - "channel_tab_podcasts_label": "Podcast" + "channel_tab_podcasts_label": "Podcast", + "generic_channels_count_0": "{{count}} canale", + "generic_channels_count_1": "{{count}} canali", + "generic_channels_count_2": "{{count}} canali" } diff --git a/locales/ja.json b/locales/ja.json index 6fc02e2d..17e60998 100644 --- a/locales/ja.json +++ b/locales/ja.json @@ -468,5 +468,6 @@ "generic_button_edit": "編集", "generic_button_save": "保存", "generic_button_rss": "RSS", - "playlist_button_add_items": "動画を追加" + "playlist_button_add_items": "動画を追加", + "generic_channels_count_0": "{{count}}個のチャンネル" } diff --git a/locales/ko.json b/locales/ko.json index e02a8316..e496bd2a 100644 --- a/locales/ko.json +++ b/locales/ko.json @@ -468,5 +468,6 @@ "generic_button_save": "저장", "generic_button_cancel": "취소", "generic_button_rss": "RSS", - "channel_tab_releases_label": "출시" + "channel_tab_releases_label": "출시", + "generic_channels_count_0": "{{count}} 채널" } diff --git a/locales/nb-NO.json b/locales/nb-NO.json index 216b559f..08b1e0e2 100644 --- a/locales/nb-NO.json +++ b/locales/nb-NO.json @@ -484,5 +484,7 @@ "generic_button_save": "Lagre", "generic_button_cancel": "Avbryt", "generic_button_rss": "RSS", - "playlist_button_add_items": "Legg til videoer" + "playlist_button_add_items": "Legg til videoer", + "generic_channels_count": "{{count}} kanal", + "generic_channels_count_plural": "{{count}} kanaler" } diff --git a/locales/pl.json b/locales/pl.json index f1924c8a..313f11cb 100644 --- a/locales/pl.json +++ b/locales/pl.json @@ -500,5 +500,8 @@ "channel_tab_releases_label": "Wydania", "generic_button_delete": "Usuń", "generic_button_save": "Zapisz", - "playlist_button_add_items": "Dodaj filmy" + "playlist_button_add_items": "Dodaj filmy", + "generic_channels_count_0": "{{count}} kanał", + "generic_channels_count_1": "{{count}} kanały", + "generic_channels_count_2": "{{count}} kanałów" } diff --git a/locales/pt-BR.json b/locales/pt-BR.json index 68a6e3ab..1e089723 100644 --- a/locales/pt-BR.json +++ b/locales/pt-BR.json @@ -112,8 +112,9 @@ "Subscription manager": "Gerenciador de inscrições", "Token manager": "Gerenciador de tokens", "Token": "Token", - "tokens_count": "{{count}} token", - "tokens_count_plural": "{{count}} tokens", + "tokens_count_0": "{{count}} token", + "tokens_count_1": "{{count}} tokens", + "tokens_count_2": "{{count}} tokens", "Import/export": "Importar/Exportar", "unsubscribe": "cancelar inscrição", "revoke": "revogar", @@ -297,20 +298,27 @@ "Yiddish": "Iídiche", "Yoruba": "Iorubá", "Zulu": "Zulu", - "generic_count_years": "{{count}} ano", - "generic_count_years_plural": "{{count}} anos", - "generic_count_months": "{{count}} mês", - "generic_count_months_plural": "{{count}} meses", - "generic_count_weeks": "{{count}} semana", - "generic_count_weeks_plural": "{{count}} semanas", - "generic_count_days": "{{count}} dia", - "generic_count_days_plural": "{{count}} dias", - "generic_count_hours": "{{count}} hora", - "generic_count_hours_plural": "{{count}} horas", - "generic_count_minutes": "{{count}} minuto", - "generic_count_minutes_plural": "{{count}} minutos", - "generic_count_seconds": "{{count}} segundo", - "generic_count_seconds_plural": "{{count}} segundos", + "generic_count_years_0": "{{count}} ano", + "generic_count_years_1": "{{count}} anos", + "generic_count_years_2": "{{count}} anos", + "generic_count_months_0": "{{count}} mês", + "generic_count_months_1": "{{count}} meses", + "generic_count_months_2": "{{count}} meses", + "generic_count_weeks_0": "{{count}} semana", + "generic_count_weeks_1": "{{count}} semanas", + "generic_count_weeks_2": "{{count}} semanas", + "generic_count_days_0": "{{count}} dia", + "generic_count_days_1": "{{count}} dias", + "generic_count_days_2": "{{count}} dias", + "generic_count_hours_0": "{{count}} hora", + "generic_count_hours_1": "{{count}} horas", + "generic_count_hours_2": "{{count}} horas", + "generic_count_minutes_0": "{{count}} minuto", + "generic_count_minutes_1": "{{count}} minutos", + "generic_count_minutes_2": "{{count}} minutos", + "generic_count_seconds_0": "{{count}} segundo", + "generic_count_seconds_1": "{{count}} segundos", + "generic_count_seconds_2": "{{count}} segundos", "Fallback comments: ": "Comentários alternativos: ", "Popular": "Populares", "Search": "Procurar", @@ -377,20 +385,27 @@ "preferences_quality_dash_label": "Qualidade de vídeo do painel preferida: ", "preferences_region_label": "País do conteúdo: ", "preferences_quality_dash_option_4320p": "4320p", - "generic_videos_count": "{{count}} vídeo", - "generic_videos_count_plural": "{{count}} vídeos", - "generic_playlists_count": "{{count}} lista de reprodução", - "generic_playlists_count_plural": "{{count}} listas de reprodução", - "generic_subscribers_count": "{{count}} inscrito", - "generic_subscribers_count_plural": "{{count}} inscritos", - "generic_subscriptions_count": "{{count}} inscrição", - "generic_subscriptions_count_plural": "{{count}} inscrições", - "subscriptions_unseen_notifs_count": "{{count}} notificação não vista", - "subscriptions_unseen_notifs_count_plural": "{{count}} notificações não vistas", - "comments_view_x_replies": "Ver {{count}} resposta", - "comments_view_x_replies_plural": "Ver {{count}} respostas", - "comments_points_count": "{{count}} ponto", - "comments_points_count_plural": "{{count}} pontos", + "generic_videos_count_0": "{{count}} vídeo", + "generic_videos_count_1": "{{count}} vídeos", + "generic_videos_count_2": "{{count}} vídeos", + "generic_playlists_count_0": "{{count}} lista de reprodução", + "generic_playlists_count_1": "{{count}} listas de reprodução", + "generic_playlists_count_2": "{{count}} listas de reprodução", + "generic_subscribers_count_0": "{{count}} inscrito", + "generic_subscribers_count_1": "{{count}} inscritos", + "generic_subscribers_count_2": "{{count}} inscritos", + "generic_subscriptions_count_0": "{{count}} inscrição", + "generic_subscriptions_count_1": "{{count}} inscrições", + "generic_subscriptions_count_2": "{{count}} inscrições", + "subscriptions_unseen_notifs_count_0": "{{count}} notificação não vista", + "subscriptions_unseen_notifs_count_1": "{{count}} notificações não vistas", + "subscriptions_unseen_notifs_count_2": "{{count}} notificações não vistas", + "comments_view_x_replies_0": "Ver {{count}} resposta", + "comments_view_x_replies_1": "Ver {{count}} respostas", + "comments_view_x_replies_2": "Ver {{count}} respostas", + "comments_points_count_0": "{{count}} ponto", + "comments_points_count_1": "{{count}} pontos", + "comments_points_count_2": "{{count}} pontos", "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: ", @@ -400,8 +415,9 @@ "crash_page_search_issue": "procurou por um erro existente no GitHub", "crash_page_report_issue": "Se nenhuma opção acima ajudou, por favor abra um novo problema no Github (preferencialmente em inglês) e inclua o seguinte texto (NÃO traduza):", "crash_page_read_the_faq": "leia as Perguntas frequentes (FAQ)", - "generic_views_count": "{{count}} visualização", - "generic_views_count_plural": "{{count}} visualizações", + "generic_views_count_0": "{{count}} visualização", + "generic_views_count_1": "{{count}} visualizações", + "generic_views_count_2": "{{count}} visualizações", "preferences_quality_option_dash": "DASH (qualidade adaptável)", "preferences_quality_option_hd720": "HD720", "preferences_quality_option_small": "Pequeno", @@ -484,5 +500,8 @@ "channel_tab_releases_label": "Lançamentos", "channel_tab_podcasts_label": "Podcasts", "generic_button_cancel": "Cancelar", - "generic_button_rss": "RSS" + "generic_button_rss": "RSS", + "generic_channels_count_0": "{{count}} canal", + "generic_channels_count_1": "{{count}} canais", + "generic_channels_count_2": "{{count}} canais" } diff --git a/locales/ru.json b/locales/ru.json index 5325a9b6..2769f3ab 100644 --- a/locales/ru.json +++ b/locales/ru.json @@ -500,5 +500,8 @@ "generic_button_cancel": "Отменить", "generic_button_rss": "RSS", "playlist_button_add_items": "Добавить видео", - "channel_tab_podcasts_label": "Подкасты" + "channel_tab_podcasts_label": "Подкасты", + "generic_channels_count_0": "{{count}} канал", + "generic_channels_count_1": "{{count}} канала", + "generic_channels_count_2": "{{count}} каналов" } diff --git a/locales/sl.json b/locales/sl.json index fec1cb62..9a912f2d 100644 --- a/locales/sl.json +++ b/locales/sl.json @@ -516,5 +516,9 @@ "generic_button_rss": "RSS", "playlist_button_add_items": "Dodaj videoposnetke", "channel_tab_podcasts_label": "Poddaje", - "channel_tab_releases_label": "Izdaje" + "channel_tab_releases_label": "Izdaje", + "generic_channels_count_0": "{{count}} kanal", + "generic_channels_count_1": "{{count}} kanala", + "generic_channels_count_2": "{{count}} kanali", + "generic_channels_count_3": "{{count}} kanalov" } diff --git a/locales/sq.json b/locales/sq.json index d28eb784..41d4161c 100644 --- a/locales/sq.json +++ b/locales/sq.json @@ -257,7 +257,7 @@ "Video mode": "Mënyrë video", "channel_tab_videos_label": "Video", "search_filters_sort_option_rating": "Vlerësim", - "search_filters_sort_option_date": "Datë ngarkimi", + "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", @@ -345,7 +345,7 @@ "View YouTube comments": "Shihni komente Youtube", "View more comments on Reddit": "Shihni më tepër komente në Reddit", "View `x` comments": { - "([^.,0-9]|^)1([^.,0-9]|$)": "Shihni `x` komente", + "([^.,0-9]|^)1([^.,0-9]|$)": "Shihni `x` koment", "": "Shihni `x` komente" }, "View Reddit comments": "Shihni komente Reddit", @@ -462,5 +462,20 @@ "channel_tab_channels_label": "Kanale", "Music in this video": "Muzikë në këtë video", "channel_tab_shorts_label": "Të shkurtra", - "channel_tab_streams_label": "Transmetime të drejtpërdrejta" + "channel_tab_streams_label": "Transmetime të drejtpërdrejta", + "generic_button_cancel": "Anuloje", + "generic_channels_count": "{{count}} kanal", + "generic_channels_count_plural": "{{count}} kanale", + "generic_button_rss": "RSS", + "generic_button_delete": "Fshije", + "generic_button_save": "Ruaje", + "generic_button_edit": "Përpunoni", + "playlist_button_add_items": "Shtoni video", + "Report statistics: ": "Statistika raportimesh: ", + "Download is disabled": "Shkarkimi është i çaktivizuar", + "Channel Sponsor": "Sponsor Kanali", + "channel_tab_releases_label": "Hedhje në qarkullim", + "Song: ": "Pjesë: ", + "Import YouTube playlist (.csv)": "Importoni luajlistë YouTube (.csv)", + "Standard YouTube license": "Licencë YouTube standarde" } diff --git a/locales/sr.json b/locales/sr.json index a2853b68..f0e5518d 100644 --- a/locales/sr.json +++ b/locales/sr.json @@ -1,90 +1,90 @@ { "LIVE": "UŽIVO", - "Shared `x` ago": "Podeljeno pre `x`", + "Shared `x` ago": "Deljeno pre `x`", "Unsubscribe": "Prekini praćenje", - "Subscribe": "Prati", + "Subscribe": "Zaprati", "View channel on YouTube": "Pogledaj kanal na YouTube-u", - "View playlist on YouTube": "Pogledaj spisak izvođenja na YouTube-u", + "View playlist on YouTube": "Pogledaj plejlistu na YouTube-u", "newest": "najnovije", "oldest": "najstarije", "popular": "popularno", "last": "poslednje", "Next page": "Sledeća stranica", "Previous page": "Prethodna stranica", - "Clear watch history?": "Izbrisati povest pregledanja?", + "Clear watch history?": "Očistiti istoriju gledanja?", "New password": "Nova lozinka", - "New passwords must match": "Nove lozinke moraju biti istovetne", - "Authorize token?": "Ovlasti žeton?", - "Authorize token for `x`?": "Ovlasti žeton za `x`?", + "New passwords must match": "Nove lozinke moraju da se podudaraju", + "Authorize token?": "Autorizovati token?", + "Authorize token for `x`?": "Autorizovati token za `x`?", "Yes": "Da", "No": "Ne", - "Import and Export Data": "Uvoz i Izvoz Podataka", + "Import and Export Data": "Uvoz i izvoz podataka", "Import": "Uvezi", - "Import Invidious data": "Uvezi podatke sa Invidious-a", - "Import YouTube subscriptions": "Uvezi praćenja sa YouTube-a", - "Import FreeTube subscriptions (.db)": "Uvezi praćenja sa FreeTube-a (.db)", - "Import NewPipe subscriptions (.json)": "Uvezi praćenja sa NewPipe-a (.json)", - "Import NewPipe data (.zip)": "Uvezi podatke sa NewPipe-a (.zip)", + "Import Invidious data": "Uvezi Invidious JSON podatke", + "Import YouTube subscriptions": "Uvezi YouTube/OPML praćenja", + "Import FreeTube subscriptions (.db)": "Uvezi FreeTube praćenja (.db)", + "Import NewPipe subscriptions (.json)": "Uvezi NewPipe praćenja (.json)", + "Import NewPipe data (.zip)": "Uvezi NewPipe podatke (.zip)", "Export": "Izvezi", - "Export subscriptions as OPML": "Izvezi praćenja kao OPML datoteku", - "Export subscriptions as OPML (for NewPipe & FreeTube)": "Izvezi praćenja kao OPML datoteku (za NewPipe i FreeTube)", - "Export data as JSON": "Izvezi podatke kao JSON datoteku", - "Delete account?": "Izbrišite nalog?", + "Export subscriptions as OPML": "Izvezi praćenja kao OPML", + "Export subscriptions as OPML (for NewPipe & FreeTube)": "Izvezi praćenja kao OPML (za NewPipe i FreeTube)", + "Export data as JSON": "Izvezi Invidious podatke kao JSON", + "Delete account?": "Izbrisati nalog?", "History": "Istorija", - "An alternative front-end to YouTube": "Zamenski korisnički sloj za YouTube", - "JavaScript license information": "Izveštaj o JavaScript odobrenju", + "An alternative front-end to YouTube": "Alternativni front-end za YouTube", + "JavaScript license information": "Informacije o JavaScript licenci", "source": "izvor", - "Log in": "Prijavi se", - "Log in/register": "Prijavi se/Otvori nalog", - "User ID": "Korisnički ID", + "Log in": "Prijava", + "Log in/register": "Prijava/registracija", + "User ID": "ID korisnika", "Password": "Lozinka", "Time (h:mm:ss):": "Vreme (č:mm:ss):", - "Text CAPTCHA": "Znakovni CAPTCHA", - "Image CAPTCHA": "Slikovni CAPTCHA", + "Text CAPTCHA": "Tekst CAPTCHA", + "Image CAPTCHA": "Slika CAPTCHA", "Sign In": "Prijava", - "Register": "Otvori nalog", - "E-mail": "E-pošta", + "Register": "Registracija", + "E-mail": "Imejl", "Preferences": "Podešavanja", - "preferences_category_player": "Podešavanja reproduktora", + "preferences_category_player": "Podešavanja plejera", "preferences_video_loop_label": "Uvek ponavljaj: ", - "preferences_autoplay_label": "Samopuštanje: ", - "preferences_continue_label": "Uvek podrazumevano puštaj sledeće: ", - "preferences_continue_autoplay_label": "Samopuštanje sledećeg video zapisa: ", - "preferences_listen_label": "Uvek podrazumevano uključen samo zvuk: ", - "preferences_local_label": "Prikaz video zapisa preko posrednika: ", - "Playlist privacy": "Podešavanja privatnosti plej liste", - "Editing playlist `x`": "Izmena plej liste `x`", - "Playlist does not exist.": "Nepostojeća plej lista.", + "preferences_autoplay_label": "Automatski pusti: ", + "preferences_continue_label": "Podrazumevano pusti sledeće: ", + "preferences_continue_autoplay_label": "Automatski pusti sledeći video snimak: ", + "preferences_listen_label": "Podrazumevano uključi samo zvuk: ", + "preferences_local_label": "Proksi video snimci: ", + "Playlist privacy": "Privatnost plejliste", + "Editing playlist `x`": "Izmenjivanje plejliste `x`", + "Playlist does not exist.": "Plejlista ne postoji.", "Erroneous challenge": "Pogrešan izazov", "Maltese": "Malteški", "Download": "Preuzmi", - "Download as: ": "Preuzmi kao: ", - "Bangla": "Bangla/Bengalski", - "preferences_quality_dash_label": "Preferirani kvalitet DASH video formata: ", - "Token manager": "Upravljanje žetonima", - "Token": "Žeton", - "Import/export": "Uvezi/Izvezi", + "Download as: ": "Preuzeti kao: ", + "Bangla": "Bengalski", + "preferences_quality_dash_label": "Preferirani DASH kvalitet video snimka: ", + "Token manager": "Upravljanje tokenima", + "Token": "Token", + "Import/export": "Uvoz/izvoz", "revoke": "opozovi", "search": "pretraga", "Log out": "Odjava", - "Source available here.": "Izvorna koda je ovde dostupna.", + "Source available here.": "Izvorni kôd je dostupan ovde.", "Trending": "U trendu", "Updated `x` ago": "Ažurirano pre `x`", - "Delete playlist `x`?": "Obriši plej listu `x`?", - "Create playlist": "Napravi plej listu", + "Delete playlist `x`?": "Izbrisati plejlistu `x`?", + "Create playlist": "Napravi plejlistu", "Show less": "Prikaži manje", "Switch Invidious Instance": "Promeni Invidious instancu", "Hide annotations": "Sakrij napomene", - "User ID is a required field": "Korisnički ID je obavezno polje", + "User ID is a required field": "ID korisnika je obavezno polje", "Wrong username or password": "Pogrešno korisničko ime ili lozinka", - "Please log in": "Molimo vas da se prijavite", + "Please log in": "Molimo, prijavite se", "channel:`x`": "kanal:`x`", - "Could not fetch comments": "Uzimanje komentara nije uspelo", - "Could not create mix.": "Pravljenje miksa nije uspelo.", - "Empty playlist": "Prazna plej lista", - "Not a playlist.": "Nije plej lista.", - "Could not pull trending pages.": "Učitavanje 'U toku' stranica nije uspelo.", - "Token is expired, please try again": "Žeton je istekao, molimo vas da pokušate ponovo", + "Could not fetch comments": "Nije moguće prikupiti komentare", + "Could not create mix.": "Nije moguće napraviti miks.", + "Empty playlist": "Prazna plejlista", + "Not a playlist.": "Nije plejlista.", + "Could not pull trending pages.": "Nije moguće povući stranice „U trendu“.", + "Token is expired, please try again": "Token je istekao, pokušajte ponovo", "English (auto-generated)": "Engleski (automatski generisano)", "Afrikaans": "Afrikans", "Albanian": "Albanski", @@ -95,19 +95,19 @@ "Bulgarian": "Bugarski", "Burmese": "Burmanski", "Catalan": "Katalonski", - "Cebuano": "Sebuano", + "Cebuano": "Cebuanski", "Chinese (Traditional)": "Kineski (Tradicionalni)", "Corsican": "Korzikanski", "Danish": "Danski", - "Kannada": "Kanada (Jezik)", + "Kannada": "Kanada", "Kazakh": "Kazaški", "Russian": "Ruski", "Scottish Gaelic": "Škotski Gelski", - "Sinhala": "Sinhaleški", + "Sinhala": "Sinhalski", "Slovak": "Slovački", "Spanish": "Španski", - "Spanish (Latin America)": "Španski (Južna Amerika)", - "Sundanese": "Sundski", + "Spanish (Latin America)": "Španski (Latinska Amerika)", + "Sundanese": "Sundanski", "Swedish": "Švedski", "Tajik": "Tadžički", "Telugu": "Telugu", @@ -116,77 +116,77 @@ "Urdu": "Urdu", "Uzbek": "Uzbečki", "Vietnamese": "Vijetnamski", - "Rating: ": "Ocena/e: ", - "View as playlist": "Pogledaj kao plej listu", - "Default": "Podrazumevan/o", - "Gaming": "Igrice", + "Rating: ": "Ocena: ", + "View as playlist": "Pogledaj kao plejlistu", + "Default": "Podrazumevano", + "Gaming": "Video igre", "Movies": "Filmovi", "%A %B %-d, %Y": "%A %B %-d, %Y", "(edited)": "(izmenjeno)", - "YouTube comment permalink": "YouTube komentar trajna veza", - "Audio mode": "Audio mod", - "Playlists": "Plej liste", + "YouTube comment permalink": "Trajni link YouTube komentara", + "Audio mode": "Režim audio snimka", + "Playlists": "Plejliste", "search_filters_sort_option_relevance": "Relevantnost", - "search_filters_sort_option_rating": "Ocene", + "search_filters_sort_option_rating": "Ocena", "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 ❤", + "`x` marked it with a ❤": "`x` je označio/la sa ❤", "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_week": "Ove nedelje", + "search_filters_date_option_month": "Ovog meseca", "search_filters_date_option_year": "Ove godine", - "search_filters_type_option_video": "Video", - "search_filters_type_option_playlist": "Plej lista", + "search_filters_type_option_video": "Video snimak", + "search_filters_type_option_playlist": "Plejlista", "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_c_commons": "Creative Commons", "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", + "search_filters_features_option_hdr": "HDR", + "next_steps_error_message": "Nakon toga treba da pokušate da: ", + "next_steps_error_message_go_to_youtube": "Odete na YouTube", "footer_documentation": "Dokumentacija", - "preferences_region_label": "Država porekla sadržaja: ", + "preferences_region_label": "Država sadržaja: ", "preferences_player_style_label": "Stil plejera: ", - "preferences_dark_mode_label": "Izgled/Tema: ", - "light": "svetlo", + "preferences_dark_mode_label": "Tema: ", + "light": "svetla", "preferences_thin_mode_label": "Kompaktni režim: ", "preferences_category_misc": "Ostala podešavanja", - "preferences_automatic_instance_redirect_label": "Automatsko prebacivanje na drugu instancu u slučaju otkazivanja (preči će nazad na redirect.invidious.io): ", - "alphabetically - reverse": "po alfabetu - obrnuto", - "Enable web notifications": "Omogući obaveštenja u veb pretraživaču", - "`x` is live": "`x` prenosi uživo", - "Manage tokens": "Upravljaj žetonima", + "preferences_automatic_instance_redirect_label": "Automatsko preusmeravanje instance (povratak na redirect.invidious.io): ", + "alphabetically - reverse": "abecedno - obrnuto", + "Enable web notifications": "Omogući veb obaveštenja", + "`x` is live": "`x` je uživo", + "Manage tokens": "Upravljaj tokenima", "Watch history": "Istorija gledanja", - "preferences_feed_menu_label": "Dovodna stranica: ", + "preferences_feed_menu_label": "Fid meni: ", "preferences_show_nick_label": "Prikaži nadimke na vrhu: ", "CAPTCHA enabled: ": "CAPTCHA omogućena: ", "Registration enabled: ": "Registracija omogućena: ", "Subscription manager": "Upravljanje praćenjima", - "Wilson score: ": "Wilsonova ocena: ", + "Wilson score: ": "Vilsonova ocena: ", "Engagement: ": "Angažovanje: ", - "Whitelisted regions: ": "Dozvoljene oblasti: ", - "Shared `x`": "Podeljeno `x`", - "Premieres in `x`": "Premera u `x`", - "Premieres `x`": "Premere u `x`", - "Hi! Looks like you have JavaScript turned off. Click here to view comments, keep in mind they may take a bit longer to load.": "Hej! Izgleda da ste onemogućili JavaScript. Kliknite ovde da vidite komentare, čuvajte na umu da ovo može da potraje duže dok se ne učitaju.", + "Whitelisted regions: ": "Dostupni regioni: ", + "Shared `x`": "Deljeno `x`", + "Premieres in `x`": "Premijera u `x`", + "Premieres `x`": "Premijera `x`", + "Hi! Looks like you have JavaScript turned off. Click here to view comments, keep in mind they may take a bit longer to load.": "Hej! Izgleda da ste isključili JavaScript. Kliknite ovde da biste videli komentare, imajte na umu da će možda potrajati malo duže da se učitaju.", "View `x` comments": { - "([^.,0-9]|^)1([^.,0-9]|$)": "Prikaži `x` komentar", - "": "Prikaži `x` komentara" + "([^.,0-9]|^)1([^.,0-9]|$)": "Pogledaj `x` komentar", + "": "Pogledaj`x` komentare" }, - "View Reddit comments": "Prikaži Reddit komentare", + "View Reddit comments": "Pogledaj Reddit komentare", "CAPTCHA is a required field": "CAPTCHA je obavezno polje", "Croatian": "Hrvatski", "Estonian": "Estonski", - "Filipino": "Filipino", + "Filipino": "Filipinski", "French": "Francuski", "Galician": "Galicijski", "German": "Nemački", "Greek": "Grčki", "Hausa": "Hausa", - "Italian": "Talijanski", + "Italian": "Italijanski", "Khmer": "Kmerski", "Kurdish": "Kurdski", "Kyrgyz": "Kirgiski", @@ -195,68 +195,68 @@ "Macedonian": "Makedonski", "Malagasy": "Malgaški", "Malay": "Malajski", - "Marathi": "Marathi", + "Marathi": "Maratski", "Mongolian": "Mongolski", "Norwegian Bokmål": "Norveški Bokmal", - "Nyanja": "Čeva", + "Nyanja": "Nijandža", "Pashto": "Paštunski", "Persian": "Persijski", - "Punjabi": "Pundžabi", + "Punjabi": "Pandžapski", "Romanian": "Rumunski", "Welsh": "Velški", "Western Frisian": "Zapadnofrizijski", - "Fallback comments: ": "Komentari u slučaju otkazivanja: ", + "Fallback comments: ": "Rezervni komentari: ", "Popular": "Popularno", "Search": "Pretraga", - "About": "O programu", - "footer_source_code": "Izvorna Koda", - "footer_original_source_code": "Originalna Izvorna Koda", - "preferences_related_videos_label": "Prikaži slične video klipove: ", - "preferences_annotations_label": "Prikaži napomene podrazumevano: ", - "preferences_extend_desc_label": "Automatski prikaži ceo opis videa: ", - "preferences_vr_mode_label": "Interaktivni video klipovi u 360 stepeni: ", - "preferences_category_visual": "Vizuelne preference", - "preferences_captions_label": "Podrazumevani titl: ", + "About": "O sajtu", + "footer_source_code": "Izvorni kôd", + "footer_original_source_code": "Originalni izvorni kôd", + "preferences_related_videos_label": "Prikaži povezane video snimke: ", + "preferences_annotations_label": "Podrazumevano prikaži napomene: ", + "preferences_extend_desc_label": "Automatski proširi opis video snimka: ", + "preferences_vr_mode_label": "Interaktivni video snimci od 360 stepeni (zahteva WebGl): ", + "preferences_category_visual": "Vizuelna podešavanja", + "preferences_captions_label": "Podrazumevani titlovi: ", "Music": "Muzika", - "search_filters_type_label": "Tip", + "search_filters_type_label": "Vrsta", "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)", + "Only show latest unwatched video from channel: ": "Prikaži samo najnoviji neodgledani video snimak sa kanala: ", + "Xhosa": "Kosa (Khosa)", "search_filters_type_option_channel": "Kanal", "Hungarian": "Mađarski", - "Maori": "Maori (Jezik)", - "Manage subscriptions": "Upravljaj zapisima", + "Maori": "Maorski", + "Manage subscriptions": "Upravljaj praćenjima", "Hindi": "Hindi", "`x` ago": "pre `x`", "Import/export data": "Uvezi/Izvezi podatke", - "`x` uploaded a video": "`x` je otpremio/la video klip", - "Delete account": "Obriši nalog", + "`x` uploaded a video": "`x` je otpremio/la video snimak", + "Delete account": "Izbriši nalog", "preferences_default_home_label": "Podrazumevana početna stranica: ", "Serbian": "Srpski", "License: ": "Licenca: ", "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: ", + "Report statistics: ": "Izveštavaj statistike: ", + "Only show latest video from channel: ": "Prikaži samo najnoviji video snimak sa kanala: ", "channel name - reverse": "ime kanala - obrnuto", - "Could not get channel info.": "Uzimanje podataka o kanalu nije uspelo.", - "View privacy policy.": "Pogledaj izveštaj o privatnosti.", + "Could not get channel info.": "Nije moguće prikupiti informacije o kanalu.", + "View privacy policy.": "Pogledaj politiku privatnosti.", "Change password": "Promeni lozinku", - "Malayalam": "Malajalam", - "View more comments on Reddit": "Prikaži više komentara na Reddit-u", + "Malayalam": "Malajalamski", + "View more comments on Reddit": "Pogledaj više komentara na Reddit-u", "Portuguese": "Portugalski", - "View YouTube comments": "Prikaži YouTube komentare", + "View YouTube comments": "Pogledaj YouTube komentare", "published - reverse": "objavljeno - obrnuto", "Dutch": "Holandski", - "preferences_volume_label": "Jačina zvuka: ", + "preferences_volume_label": "Jačina zvuka plejera: ", "preferences_locale_label": "Jezik: ", - "adminprefs_modified_source_code_url_label": "URL veza do skladišta sa Izmenjenom Izvornom Kodom", + "adminprefs_modified_source_code_url_label": "URL adresa do repozitorijuma izmenjenog izvornog koda", "channel_tab_community_label": "Zajednica", - "Video mode": "Video mod", - "Fallback captions: ": "Titl u slučaju da glavni nije dostupan: ", + "Video mode": "Režim video snimka", + "Fallback captions: ": "Rezervni titlovi: ", "Private": "Privatno", - "alphabetically": "po alfabetu", - "No such user": "Nepostojeći korisnik", + "alphabetically": "abecedno", + "No such user": "Ne postoji korisnik", "Subscriptions": "Praćenja", "search_filters_date_option_today": "Danas", "Finnish": "Finski", @@ -265,30 +265,30 @@ "Shona": "Šona", "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.": "Objavljeno pod licencom AGPLv3 na GitHub-u.", "Slovenian": "Slovenački", - "View JavaScript license information.": "Pogledaj informacije licence vezane za JavaScript.", + "View JavaScript license information.": "Pogledaj informacije o JavaScript licenci.", "Chinese (Simplified)": "Kineski (Pojednostavljeni)", "preferences_comments_label": "Podrazumevani komentari: ", "Incorrect password": "Netačna lozinka", "Show replies": "Prikaži odgovore", - "Invidious Private Feed for `x`": "Invidious Privatni Dovod za `x`", + "Invidious Private Feed for `x`": "Invidious privatni fid za `x`", "Watch on YouTube": "Gledaj na YouTube-u", "Wrong answer": "Pogrešan odgovor", - "preferences_quality_label": "Preferirani video kvalitet: ", + "preferences_quality_label": "Preferirani kvalitet video snimka: ", "Hide replies": "Sakrij odgovore", "Erroneous CAPTCHA": "Pogrešna CAPTCHA", - "Erroneous token": "Pogrešan žeton", + "Erroneous token": "Pogrešan token", "Czech": "Češki", "Latin": "Latinski", - "channel_tab_videos_label": "Video klipovi", + "channel_tab_videos_label": "Video snimci", "search_filters_features_option_four_k": "4К", "footer_donate_page": "Doniraj", "English": "Engleski", "Arabic": "Arapski", - "Unlisted": "Nenavedeno", - "Hidden field \"challenge\" is a required field": "Sakriveno \"challenge\" polje je obavezno", - "Hidden field \"token\" is a required field": "Sakriveno \"token\" polje je obavezno", + "Unlisted": "Po pozivu", + "Hidden field \"challenge\" is a required field": "Skriveno polje „izazov“ je obavezno polje", + "Hidden field \"token\" is a required field": "Skriveno polje „token“ je obavezno polje", "Georgian": "Gruzijski", "Hawaiian": "Havajski", "Hebrew": "Hebrejski", @@ -297,68 +297,211 @@ "Japanese": "Japanski", "Javanese": "Javanski", "Sindhi": "Sindi", - "Swahili": "Svahili", + "Swahili": "Suvali", "Yiddish": "Jidiš", "Zulu": "Zulu", - "search_filters_features_option_subtitles": "Titl/Prevod", - "Password cannot be longer than 55 characters": "Lozinka ne može biti duža od 55 karaktera", + "search_filters_features_option_subtitles": "Titlovi/Skriveni titlovi", + "Password cannot be longer than 55 characters": "Lozinka ne može biti duža od 55 znakova", "This channel does not exist.": "Ovaj kanal ne postoji.", "Belarusian": "Beloruski", "Gujarati": "Gudžarati", "Haitian Creole": "Haićanski Kreolski", "Somali": "Somalijski", - "Top": "Vrh", - "footer_modfied_source_code": "Izmenjena Izvorna Koda", + "Top": "Top", + "footer_modfied_source_code": "Izmenjeni izvorni kôd", "preferences_category_subscription": "Podešavanja praćenja", "preferences_annotations_subscribed_label": "Podrazumevano prikazati napomene za kanale koje pratite? ", - "preferences_max_results_label": "Broj video klipova prikazanih u dovodnoj listi: ", - "preferences_sort_label": "Sortiraj video klipove po: ", - "preferences_unseen_only_label": "Prikaži samo video klipove koji nisu pogledani: ", - "preferences_notifications_only_label": "Prikaži samo obaveštenja (ako ih uopšte ima): ", + "preferences_max_results_label": "Broj video snimaka prikazanih u fidu: ", + "preferences_sort_label": "Sortiraj video snimke po: ", + "preferences_unseen_only_label": "Prikaži samo neodgledano: ", + "preferences_notifications_only_label": "Prikaži samo obaveštenja (ako ih ima): ", "preferences_category_data": "Podešavanja podataka", - "Clear watch history": "Obriši istoriju gledanja", - "preferences_category_admin": "Administratorska podešavanja", + "Clear watch history": "Očisti istoriju gledanja", + "preferences_category_admin": "Podešavanja administratora", "published": "objavljeno", - "search_filters_sort_label": "Poredaj prema", + "search_filters_sort_label": "Sortiranje po", "search_filters_type_option_show": "Emisija", - "search_filters_duration_option_short": "Kratko (< 4 minute)", + "search_filters_duration_option_short": "Kratko (< 4 minuta)", "Current version: ": "Trenutna verzija: ", - "Top enabled: ": "Vrh omogućen: ", + "Top enabled: ": "Top omogućeno: ", "Public": "Javno", - "Delete playlist": "Obriši plej listu", + "Delete playlist": "Izbriši plejlistu", "Title": "Naslov", "Show annotations": "Prikaži napomene", "Password cannot be empty": "Lozinka ne može biti prazna", - "Deleted or invalid channel": "Obrisan ili nepostojeći kanal", + "Deleted or invalid channel": "Izbrisan ili nevažeći kanal", "Esperanto": "Esperanto", "Hmong": "Hmong", "Luxembourgish": "Luksemburški", "Nepali": "Nepalski", "Samoan": "Samoanski", "News": "Vesti", - "permalink": "trajna veza", + "permalink": "trajni link", "Password is a required field": "Lozinka je obavezno polje", "Amharic": "Amharski", - "Indonesian": "Indonežanski", + "Indonesian": "Indonezijski", "Irish": "Irski", "Korean": "Korejski", "Southern Sotho": "Južni Soto", "Thai": "Tajski", "preferences_speed_label": "Podrazumevana brzina: ", "Dark mode: ": "Tamni režim: ", - "dark": "tamno", - "Redirect homepage to feed: ": "Prebaci sa početne stranice na dovodnu listu: ", + "dark": "tamna", + "Redirect homepage to feed: ": "Preusmeri početnu stranicu na fid: ", "channel name": "ime kanala", - "View all playlists": "Pregledaj sve plej liste", + "View all playlists": "Pogledaj sve plejliste", "Show more": "Prikaži više", "Genre: ": "Žanr: ", "Family friendly? ": "Pogodno za porodicu? ", - "next_steps_error_message_refresh": "Osveži stranicu", + "next_steps_error_message_refresh": "Osvežite", "youtube": "YouTube", "reddit": "Reddit", - "unsubscribe": "prekini sa praćenjem", - "Blacklisted regions: ": "Zabranjene oblasti: ", + "unsubscribe": "prekini praćenje", + "Blacklisted regions: ": "Nedostupni regioni: ", "Polish": "Poljski", "Yoruba": "Joruba", - "search_filters_title": "Filter" + "search_filters_title": "Filteri", + "Korean (auto-generated)": "Korejski (automatski generisano)", + "search_filters_features_option_three_sixty": "360°", + "preferences_quality_dash_option_worst": "Najgore", + "channel_tab_podcasts_label": "Podkasti", + "preferences_save_player_pos_label": "Sačuvaj poziciju reprodukcije: ", + "Spanish (Mexico)": "Španski (Meksiko)", + "generic_subscriptions_count_0": "{{count}} praćenje", + "generic_subscriptions_count_1": "{{count}} praćenja", + "generic_subscriptions_count_2": "{{count}} praćenja", + "search_filters_apply_button": "Primeni izabrane filtere", + "Download is disabled": "Preuzimanje je onemogućeno", + "comments_points_count_0": "{{count}} poen", + "comments_points_count_1": "{{count}} poena", + "comments_points_count_2": "{{count}} poena", + "preferences_quality_dash_option_2160p": "2160p", + "German (auto-generated)": "Nemački (automatski generisano)", + "Japanese (auto-generated)": "Japanski (automatski generisano)", + "preferences_quality_option_medium": "Srednje", + "search_message_change_filters_or_query": "Pokušajte da proširite upit za pretragu i/ili promenite filtere.", + "crash_page_before_reporting": "Pre nego što prijavite grešku, uverite se da ste:", + "preferences_quality_dash_option_best": "Najbolje", + "Channel Sponsor": "Sponzor kanala", + "generic_videos_count_0": "{{count}} video snimak", + "generic_videos_count_1": "{{count}} video snimka", + "generic_videos_count_2": "{{count}} video snimaka", + "videoinfo_started_streaming_x_ago": "Započeto strimovanje pre `x`", + "videoinfo_youTube_embed_link": "Ugrađeno", + "channel_tab_streams_label": "Strimovi uživo", + "playlist_button_add_items": "Dodaj video snimke", + "generic_count_minutes_0": "{{count}} minut", + "generic_count_minutes_1": "{{count}} minuta", + "generic_count_minutes_2": "{{count}} minuta", + "preferences_quality_dash_option_720p": "720p", + "preferences_watch_history_label": "Omogući istoriju gledanja: ", + "user_saved_playlists": "Sačuvanih plejlista: `x`", + "Spanish (Spain)": "Španski (Španija)", + "invidious": "Invidious", + "crash_page_refresh": "pokušali da osvežite stranicu", + "Chinese (Hong Kong)": "Kineski (Hong Kong)", + "Artist: ": "Izvođač: ", + "generic_count_months_0": "{{count}} mesec", + "generic_count_months_1": "{{count}} meseca", + "generic_count_months_2": "{{count}} meseci", + "search_message_use_another_instance": " Takođe, možete pretraživati na drugoj instanci.", + "generic_subscribers_count_0": "{{count}} pratilac", + "generic_subscribers_count_1": "{{count}} pratioca", + "generic_subscribers_count_2": "{{count}} pratilaca", + "download_subtitles": "Titlovi - `x` (.vtt)", + "generic_button_save": "Sačuvaj", + "crash_page_search_issue": "pretražili postojeće izveštaje o problemima na GitHub-u", + "generic_button_cancel": "Otkaži", + "none": "nijedno", + "English (United States)": "Engleski (Sjedinjene Američke Države)", + "subscriptions_unseen_notifs_count_0": "{{count}} neviđeno obaveštenje", + "subscriptions_unseen_notifs_count_1": "{{count}} neviđena obaveštenja", + "subscriptions_unseen_notifs_count_2": "{{count}} neviđenih obaveštenja", + "Album: ": "Album: ", + "preferences_quality_option_dash": "DASH (adaptivni kvalitet)", + "preferences_quality_dash_option_1080p": "1080p", + "Video unavailable": "Video snimak nedostupan", + "tokens_count_0": "{{count}} token", + "tokens_count_1": "{{count}} tokena", + "tokens_count_2": "{{count}} tokena", + "Chinese (China)": "Kineski (Kina)", + "Italian (auto-generated)": "Italijanski (automatski generisano)", + "channel_tab_shorts_label": "Shorts", + "preferences_quality_dash_option_1440p": "1440p", + "preferences_quality_dash_option_360p": "360p", + "search_message_no_results": "Nisu pronađeni rezultati.", + "channel_tab_releases_label": "Izdanja", + "preferences_quality_dash_option_144p": "144p", + "Interlingue": "Interlingva", + "Song: ": "Pesma: ", + "generic_channels_count_0": "{{count}} kanal", + "generic_channels_count_1": "{{count}} kanala", + "generic_channels_count_2": "{{count}} kanala", + "Chinese (Taiwan)": "Kineski (Tajvan)", + "Turkish (auto-generated)": "Turski (automatski generisano)", + "Indonesian (auto-generated)": "Indonezijski (automatski generisano)", + "Portuguese (auto-generated)": "Portugalski (automatski generisano)", + "generic_count_years_0": "{{count}} godina", + "generic_count_years_1": "{{count}} godine", + "generic_count_years_2": "{{count}} godina", + "videoinfo_invidious_embed_link": "Ugrađeni link", + "Popular enabled: ": "Popularno omogućeno: ", + "Spanish (auto-generated)": "Španski (automatski generisano)", + "preferences_quality_option_small": "Malo", + "English (United Kingdom)": "Engleski (Ujedinjeno Kraljevstvo)", + "channel_tab_playlists_label": "Plejliste", + "generic_button_edit": "Izmeni", + "generic_playlists_count_0": "{{count}} plejlista", + "generic_playlists_count_1": "{{count}} plejliste", + "generic_playlists_count_2": "{{count}} plejlista", + "preferences_quality_option_hd720": "HD720", + "search_filters_features_option_purchased": "Kupljeno", + "search_filters_date_option_none": "Bilo koji datum", + "preferences_quality_dash_option_auto": "Automatski", + "Cantonese (Hong Kong)": "Kantonski (Hong Kong)", + "crash_page_report_issue": "Ako ništa od gorenavedenog nije pomoglo, otvorite novi izveštaj o problemu na GitHub-u (po mogućnosti na engleskom) i uključite sledeći tekst u svoju poruku (NE prevodite taj tekst):", + "crash_page_switch_instance": "pokušali da koristite drugu instancu", + "generic_count_weeks_0": "{{count}} nedelja", + "generic_count_weeks_1": "{{count}} nedelje", + "generic_count_weeks_2": "{{count}} nedelja", + "videoinfo_watch_on_youTube": "Gledaj na YouTube-u", + "Music in this video": "Muzika u ovom video snimku", + "generic_button_rss": "RSS", + "preferences_quality_dash_option_4320p": "4320p", + "generic_count_hours_0": "{{count}} sat", + "generic_count_hours_1": "{{count}} sata", + "generic_count_hours_2": "{{count}} sati", + "French (auto-generated)": "Francuski (automatski generisano)", + "crash_page_read_the_faq": "pročitali Često Postavljana Pitanja (ČPP)", + "user_created_playlists": "Napravljenih plejlista: `x`", + "channel_tab_channels_label": "Kanali", + "search_filters_type_option_all": "Bilo koja vrsta", + "Russian (auto-generated)": "Ruski (automatski generisano)", + "preferences_quality_dash_option_480p": "480p", + "comments_view_x_replies_0": "Pogledaj {{count}} odgovor", + "comments_view_x_replies_1": "Pogledaj {{count}} odgovora", + "comments_view_x_replies_2": "Pogledaj {{count}} odgovora", + "Portuguese (Brazil)": "Portugalski (Brazil)", + "search_filters_features_option_vr180": "VR180", + "error_video_not_in_playlist": "Traženi video snimak ne postoji na ovoj plejlisti. Kliknite ovde za početnu stranicu plejliste.", + "Dutch (auto-generated)": "Holandski (automatski generisano)", + "generic_count_days_0": "{{count}} dan", + "generic_count_days_1": "{{count}} dana", + "generic_count_days_2": "{{count}} dana", + "Vietnamese (auto-generated)": "Vijetnamski (automatski generisano)", + "search_filters_duration_option_none": "Bilo koje trajanje", + "preferences_quality_dash_option_240p": "240p", + "Chinese": "Kineski", + "generic_button_delete": "Izbriši", + "Import YouTube playlist (.csv)": "Uvezi YouTube plejlistu (.csv)", + "Standard YouTube license": "Standardna YouTube licenca", + "search_filters_duration_option_medium": "Srednje (4 - 20 minuta)", + "generic_count_seconds_0": "{{count}} sekunda", + "generic_count_seconds_1": "{{count}} sekunde", + "generic_count_seconds_2": "{{count}} sekundi", + "search_filters_date_label": "Datum otpremanja", + "crash_page_you_found_a_bug": "Izgleda da ste pronašli grešku u Invidious-u!", + "generic_views_count_0": "{{count}} pregled", + "generic_views_count_1": "{{count}} pregleda", + "generic_views_count_2": "{{count}} pregleda" } diff --git a/locales/sr_Cyrl.json b/locales/sr_Cyrl.json index 218f31c9..bf439b28 100644 --- a/locales/sr_Cyrl.json +++ b/locales/sr_Cyrl.json @@ -1,166 +1,166 @@ { "LIVE": "УЖИВО", - "Shared `x` ago": "Подељено пре `x`", + "Shared `x` ago": "Дељено пре `x`", "Unsubscribe": "Прекини праћење", - "Subscribe": "Прати", + "Subscribe": "Запрати", "View channel on YouTube": "Погледај канал на YouTube-у", - "View playlist on YouTube": "Погледај списак извођења на YоуТубе-у", + "View playlist on YouTube": "Погледај плејлисту на YouTube-у", "newest": "најновије", "oldest": "најстарије", "popular": "популарно", "last": "последње", - "Next page": "Следећа страна", - "Previous page": "Претходна страна", - "Clear watch history?": "Избрисати повест прегледања?", + "Next page": "Следећа страница", + "Previous page": "Претходна страница", + "Clear watch history?": "Очистити историју гледања?", "New password": "Нова лозинка", - "New passwords must match": "Нове лозинке морају бити истоветне", - "Authorize token?": "Овласти жетон?", - "Authorize token for `x`?": "Овласти жетон за `x`?", + "New passwords must match": "Нове лозинке морају да се подударају", + "Authorize token?": "Ауторизовати токен?", + "Authorize token for `x`?": "Ауторизовати токен за `x`?", "Yes": "Да", "No": "Не", "Import and Export Data": "Увоз и извоз података", "Import": "Увези", - "Import Invidious data": "Увези податке са Individious-а", - "Import YouTube subscriptions": "Увези праћења са YouTube-а", - "Import FreeTube subscriptions (.db)": "Увези праћења са FreeTube-а (.db)", - "Import NewPipe subscriptions (.json)": "Увези праћења са NewPipe-а (.json)", - "Import NewPipe data (.zip)": "Увези податке са NewPipe-a (.zip)", + "Import Invidious data": "Увези Invidious JSON податке", + "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": "Извези праћења као ОПМЛ датотеку", - "Export subscriptions as OPML (for NewPipe & FreeTube)": "Извези праћења као ОПМЛ датотеку (за NewPipe и FreeTube)", - "Export data as JSON": "Извези податке као JSON датотеку", - "Delete account?": "Избришите налог?", + "Export subscriptions as OPML": "Извези праћења као OPML", + "Export subscriptions as OPML (for NewPipe & FreeTube)": "Извези праћења као OPML (за NewPipe и FreeTube)", + "Export data as JSON": "Извези Invidious податке као JSON", + "Delete account?": "Избрисати налог?", "History": "Историја", - "An alternative front-end to YouTube": "Заменски кориснички слој за YouTube", - "JavaScript license information": "Извештај о JavaScript одобрењу", + "An alternative front-end to YouTube": "Алтернативни фронт-енд за YouTube", + "JavaScript license information": "Информације о JavaScript лиценци", "source": "извор", - "Log in": "Пријави се", - "Log in/register": "Пријави се/Отворите налог", - "User ID": "Кориснички ИД", + "Log in": "Пријава", + "Log in/register": "Пријава/регистрација", + "User ID": "ID корисника", "Password": "Лозинка", "Time (h:mm:ss):": "Време (ч:мм:сс):", - "Text CAPTCHA": "Знаковни ЦАПТЧА", - "Image CAPTCHA": "Сликовни CAPTCHA", + "Text CAPTCHA": "Текст CAPTCHA", + "Image CAPTCHA": "Слика CAPTCHA", "Sign In": "Пријава", - "Register": "Отвори налог", - "E-mail": "Е-пошта", + "Register": "Регистрација", + "E-mail": "Имејл", "Preferences": "Подешавања", - "preferences_category_player": "Подешавања репродуктора", + "preferences_category_player": "Подешавања плејера", "preferences_video_loop_label": "Увек понављај: ", - "preferences_autoplay_label": "Самопуштање: ", - "preferences_continue_label": "Увек подразумевано пуштај следеће: ", - "preferences_continue_autoplay_label": "Самопуштање следећег видео записа: ", - "preferences_listen_label": "Увек подразумевано укључен само звук: ", - "preferences_local_label": "Приказ видео записа преко посредника: ", + "preferences_autoplay_label": "Аутоматски пусти: ", + "preferences_continue_label": "Подразумевано пусти следеће: ", + "preferences_continue_autoplay_label": "Аутоматски пусти следећи видео снимак: ", + "preferences_listen_label": "Подразумевано укључи само звук: ", + "preferences_local_label": "Прокси видео снимци: ", "preferences_speed_label": "Подразумевана брзина: ", - "preferences_quality_label": "Преферирани видео квалитет: ", - "preferences_volume_label": "Јачина звука: ", + "preferences_quality_label": "Преферирани квалитет видео снимка: ", + "preferences_volume_label": "Јачина звука плејера: ", "preferences_comments_label": "Подразумевани коментари: ", "youtube": "YouTube", "reddit": "Reddit", - "preferences_captions_label": "Подразумевани титл: ", - "Fallback captions: ": "Титл у случају да главни није доступан: ", - "preferences_related_videos_label": "Прикажи сличне видео клипове: ", - "preferences_annotations_label": "Прикажи напомене подразумевано: ", - "preferences_category_visual": "Визуелне преференце", + "preferences_captions_label": "Подразумевани титлови: ", + "Fallback captions: ": "Резервни титлови: ", + "preferences_related_videos_label": "Прикажи повезане видео снимке: ", + "preferences_annotations_label": "Подразумевано прикажи напомене: ", + "preferences_category_visual": "Визуелна подешавања", "preferences_player_style_label": "Стил плејера: ", "Dark mode: ": "Тамни режим: ", - "preferences_dark_mode_label": "Изглед/Тема: ", - "dark": "тамно", - "light": "светло", + "preferences_dark_mode_label": "Тема: ", + "dark": "тамна", + "light": "светла", "preferences_thin_mode_label": "Компактни режим: ", "preferences_category_subscription": "Подешавања праћења", "preferences_annotations_subscribed_label": "Подразумевано приказати напомене за канале које пратите? ", - "Redirect homepage to feed: ": "Пребаци са почетне странице на доводну листу: ", - "preferences_max_results_label": "Број видео клипова приказаних у доводној листи: ", - "preferences_sort_label": "Сортирај видео клипове по: ", + "Redirect homepage to feed: ": "Преусмери почетну страницу на фид: ", + "preferences_max_results_label": "Број видео снимака приказаних у фиду: ", + "preferences_sort_label": "Сортирај видео снимке по: ", "published": "објављено", "published - reverse": "објављено - обрнуто", - "alphabetically": "по алфабету", - "alphabetically - reverse": "по алфабету - обрнуто", + "alphabetically": "абецедно", + "alphabetically - reverse": "абецедно - обрнуто", "channel name": "име канала", "channel name - reverse": "име канала - обрнуто", - "Only show latest video from channel: ": "Приказуј последње видео клипове само са канала: ", - "Only show latest unwatched video from channel: ": "Прикажи само последње видео клипове који нису погледани са канала: ", - "preferences_unseen_only_label": "Прикажи само видео клипове који нису погледани: ", - "preferences_notifications_only_label": "Прикажи само обавештења (ако их уопште има): ", - "Enable web notifications": "Омогући обавештења у веб претраживачу", - "`x` uploaded a video": "`x` је отпремио/ла видео клип", - "`x` is live": "`x` преноси уживо", + "Only show latest video from channel: ": "Прикажи само најновији видео снимак са канала: ", + "Only show latest unwatched video from channel: ": "Прикажи само најновији неодгледани видео снимак са канала: ", + "preferences_unseen_only_label": "Прикажи само недогледано: ", + "preferences_notifications_only_label": "Прикажи само обавештења (ако их има): ", + "Enable web notifications": "Омогући веб обавештења", + "`x` uploaded a video": "`x` је отпремио/ла видео снимак", + "`x` is live": "`x` је уживо", "preferences_category_data": "Подешавања података", - "Clear watch history": "Обриши историју гледања", + "Clear watch history": "Очисти историју гледања", "Import/export data": "Увези/Извези податке", "Change password": "Промени лозинку", - "Manage subscriptions": "Управљај записима", - "Manage tokens": "Управљај жетонима", + "Manage subscriptions": "Управљај праћењима", + "Manage tokens": "Управљај токенима", "Watch history": "Историја гледања", - "Delete account": "Обриши налог", - "preferences_category_admin": "Администраторска подешавања", + "Delete account": "Избриши налог", + "preferences_category_admin": "Подешавања администратора", "preferences_default_home_label": "Подразумевана почетна страница: ", - "preferences_feed_menu_label": "Доводна страница: ", + "preferences_feed_menu_label": "Фид мени: ", "CAPTCHA enabled: ": "CAPTCHA омогућена: ", "Login enabled: ": "Пријава омогућена: ", "Registration enabled: ": "Регистрација омогућена: ", "Save preferences": "Сачувај подешавања", "Subscription manager": "Управљање праћењима", - "Token manager": "Управљање жетонима", - "Token": "Жетон", - "Import/export": "Увези/Извези", - "unsubscribe": "прекини са праћењем", + "Token manager": "Управљање токенима", + "Token": "Токен", + "Import/export": "Увоз/извоз", + "unsubscribe": "прекини праћење", "revoke": "опозови", "Subscriptions": "Праћења", "search": "претрага", "Log out": "Одјава", - "Source available here.": "Изворна кода је овде доступна.", - "View JavaScript license information.": "Погледај информације лиценце везане за JavaScript.", - "View privacy policy.": "Погледај извештај о приватности.", + "Source available here.": "Изворни кôд је доступан овде.", + "View JavaScript license information.": "Погледај информације о JavaScript лиценци.", + "View privacy policy.": "Погледај политику приватности.", "Trending": "У тренду", "Public": "Јавно", - "Unlisted": "Ненаведено", + "Unlisted": "По позиву", "Private": "Приватно", - "View all playlists": "Прегледај све плеј листе", + "View all playlists": "Погледај све плејлисте", "Updated `x` ago": "Ажурирано пре `x`", - "Delete playlist `x`?": "Обриши плеј листу `x`?", - "Delete playlist": "Обриши плеј листу", - "Create playlist": "Направи плеј листу", + "Delete playlist `x`?": "Избрисати плејлисту `x`?", + "Delete playlist": "Избриши плејлисту", + "Create playlist": "Направи плејлисту", "Title": "Наслов", - "Playlist privacy": "Подешавања приватности плеј листе", - "Editing playlist `x`": "Измена плеј листе `x`", + "Playlist privacy": "Приватност плејлисте", + "Editing playlist `x`": "Измењивање плејлисте `x`", "Watch on YouTube": "Гледај на YouTube-у", "Hide annotations": "Сакриј напомене", "Show annotations": "Прикажи напомене", "Genre: ": "Жанр: ", "License: ": "Лиценца: ", "Engagement: ": "Ангажовање: ", - "Whitelisted regions: ": "Дозвољене области: ", - "Blacklisted regions: ": "Забрањене области: ", - "Premieres in `x`": "Премера у `x`", - "Hi! Looks like you have JavaScript turned off. Click here to view comments, keep in mind they may take a bit longer to load.": "Хеј! Изгледа да сте онемогућили JavaScript. Кликните овде да видите коментаре, чувајте на уму да ово може да потраје дуже док се не учитају.", - "View YouTube comments": "Прикажи YouTube коментаре", - "View more comments on Reddit": "Прикажи више коментара на Reddit-у", - "View Reddit comments": "Прикажи Reddit коментаре", + "Whitelisted regions: ": "Доступни региони: ", + "Blacklisted regions: ": "Недоступни региони: ", + "Premieres in `x`": "Премијера у `x`", + "Hi! Looks like you have JavaScript turned off. Click here to view comments, keep in mind they may take a bit longer to load.": "Хеј! Изгледа да сте искључили JavaScript. Кликните овде да бисте видели коментаре, имајте на уму да ће можда потрајати мало дуже да се учитају.", + "View YouTube comments": "Погледај YouTube коментаре", + "View more comments on Reddit": "Погледај више коментара на Reddit-у", + "View Reddit comments": "Погледај Reddit коментаре", "Hide replies": "Сакриј одговоре", "Show replies": "Прикажи одговоре", "Incorrect password": "Нетачна лозинка", "Current version: ": "Тренутна верзија: ", - "Wilson score: ": "Wилсонова оцена: ", + "Wilson score: ": "Вилсонова оцена: ", "Burmese": "Бурмански", - "preferences_quality_dash_label": "Преферирани квалитет DASH видео формата: ", - "Erroneous token": "Погрешан жетон", + "preferences_quality_dash_label": "Преферирани DASH квалитет видео снимка: ", + "Erroneous token": "Погрешан токен", "CAPTCHA is a required field": "CAPTCHA је обавезно поље", - "No such user": "Непостојећи корисник", + "No such user": "Не постоји корисник", "Chinese (Traditional)": "Кинески (Традиционални)", - "adminprefs_modified_source_code_url_label": "УРЛ веза до складишта са Измењеном Изворном Кодом", + "adminprefs_modified_source_code_url_label": "URL адреса до репозиторијума измењеног изворног кода", "Lao": "Лаоски", "Czech": "Чешки", - "Kannada": "Канада (Језик)", + "Kannada": "Канада", "Polish": "Пољски", - "Cebuano": "Себуано", + "Cebuano": "Цебуански", "preferences_show_nick_label": "Прикажи надимке на врху: ", - "Report statistics: ": "Извештавај о статистици: ", + "Report statistics: ": "Извештавај статистике: ", "Show more": "Прикажи више", "Wrong answer": "Погрешан одговор", - "Hidden field \"token\" is a required field": "Сакривено \"token\" поље је обавезно", + "Hidden field \"token\" is a required field": "Скривено поље „токен“ је обавезно поље", "English": "Енглески", "Albanian": "Албански", "Amharic": "Амхарски", @@ -176,38 +176,38 @@ "Georgian": "Грузијски", "Greek": "Грчки", "Hausa": "Хауса", - "search_filters_type_option_video": "Видео", - "search_filters_type_option_playlist": "Плеј листа", + "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_c_commons": "Creative Commons", "search_filters_features_option_live": "Уживо", "search_filters_features_option_location": "Локација", - "next_steps_error_message": "Након чега би требали пробати: ", + "next_steps_error_message": "Након тога би требало да покушате да: ", "footer_donate_page": "Донирај", "footer_documentation": "Документација", - "footer_modfied_source_code": "Измењена Изворна Кода", - "preferences_region_label": "Држава порекла садржаја: ", + "footer_modfied_source_code": "Измењени изворни кôд", + "preferences_region_label": "Држава садржаја: ", "preferences_category_misc": "Остала подешавања", - "User ID is a required field": "Кориснички ИД је обавезно поље", + "User ID is a required field": "ID корисника је обавезно поље", "Password is a required field": "Лозинка је обавезно поље", "Wrong username or password": "Погрешно корисничко име или лозинка", "Password cannot be empty": "Лозинка не може бити празна", - "Password cannot be longer than 55 characters": "Лозинка не може бити дужа од 55 карактера", - "Invidious Private Feed for `x`": "Инвидиоус Приватни Довод за `x`", - "Deleted or invalid channel": "Обрисан или непостојећи канал", + "Password cannot be longer than 55 characters": "Лозинка не може бити дужа од 55 знакова", + "Invidious Private Feed for `x`": "Invidious приватни фид за `x`", + "Deleted or invalid channel": "Избрисан или неважећи канал", "This channel does not exist.": "Овај канал не постоји.", - "Could not create mix.": "Прављење микса није успело.", - "Empty playlist": "Празна плеј листа", - "Not a playlist.": "Није плеј листа.", - "Playlist does not exist.": "Непостојећа плеј листа.", - "Could not pull trending pages.": "Учитавање 'У току' страница није успело.", - "Hidden field \"challenge\" is a required field": "Сакривено \"challenge\" поље је обавезно", + "Could not create mix.": "Није могуће направити микс.", + "Empty playlist": "Празна плејлиста", + "Not a playlist.": "Није плејлиста.", + "Playlist does not exist.": "Плејлиста не постоји.", + "Could not pull trending pages.": "Није могуће повући странице „У тренду“.", + "Hidden field \"challenge\" is a required field": "Скривено поље „изазов“ је обавезно поље", "Telugu": "Телугу", "Turkish": "Турски", "Urdu": "Урду", - "Western Frisian": "Западнофрисијски", - "Xhosa": "Коса (Језик)", + "Western Frisian": "Западнофризијски", + "Xhosa": "Коса (Кхоса)", "Yiddish": "Јидиш", "Hawaiian": "Хавајски", "Hmong": "Хмонг", @@ -217,58 +217,58 @@ "Khmer": "Кмерски", "Kyrgyz": "Киргиски", "Macedonian": "Македонски", - "Maori": "Маори (Језик)", - "Marathi": "Маратхи", + "Maori": "Маорски", + "Marathi": "Маратски", "Nepali": "Непалски", "Norwegian Bokmål": "Норвешки Бокмал", - "Nyanja": "Чева", + "Nyanja": "Нијанџа", "Russian": "Руски", "Scottish Gaelic": "Шкотски Гелски", "Shona": "Шона", "Slovak": "Словачки", - "Spanish (Latin America)": "Шпански (Јужна Америка)", - "Sundanese": "Сундски", - "Swahili": "Свахили", + "Spanish (Latin America)": "Шпански (Латинска Америка)", + "Sundanese": "Сундански", + "Swahili": "Сували", "Tajik": "Таџички", "Search": "Претрага", - "Rating: ": "Ocena/e: ", - "Default": "Подразумеван/о", + "Rating: ": "Оцена: ", + "Default": "Подразумевано", "News": "Вести", "Download": "Преузми", "(edited)": "(измењено)", - "`x` marked it with a ❤": "`x` је означио/ла ово са ❤", - "Audio mode": "Аудио мод", - "channel_tab_videos_label": "Видео клипови", + "`x` marked it with a ❤": "`x` је означио/ла са ❤", + "Audio mode": "Режим аудио снимка", + "channel_tab_videos_label": "Видео снимци", "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": "Перзијски", + "Persian": "Персијски", "View `x` comments": { - "": "Прикажи `x` коментара", - "([^.,0-9]|^)1([^.,0-9]|$)": "Прикажи `x` коментар" + "": "Погледај `x` коментаре", + "([^.,0-9]|^)1([^.,0-9]|$)": "Погледај `x` коментар" }, "search_filters_type_option_channel": "Канал", "Haitian Creole": "Хаићански Креолски", "Armenian": "Јерменски", - "next_steps_error_message_go_to_youtube": "Иди на YouTube", - "Indonesian": "Индонежански", - "preferences_vr_mode_label": "Интерактивни видео клипови у 360 степени: ", + "next_steps_error_message_go_to_youtube": "Одете на YouTube", + "Indonesian": "Индонезијски", + "preferences_vr_mode_label": "Интерактивни видео снимци од 360 степени (захтева WebGL): ", "Switch Invidious Instance": "Промени Invidious инстанцу", "Portuguese": "Португалски", - "search_filters_date_option_week": "Ове седмице", + "search_filters_date_option_week": "Ове недеље", "search_filters_type_option_show": "Емисија", - "Fallback comments: ": "Коментари у случају отказивања: ", - "search_filters_features_option_hdr": "Видео Високе Резолуције", - "About": "О програму", + "Fallback comments: ": "Резервни коментари: ", + "search_filters_features_option_hdr": "HDR", + "About": "О сајту", "Kazakh": "Казашки", - "Shared `x`": "Подељено `x`", - "Playlists": "Плеј листе", + "Shared `x`": "Дељено `x`", + "Playlists": "Плејлисте", "Yoruba": "Јоруба", "Erroneous challenge": "Погрешан изазов", "Danish": "Дански", - "Could not get channel info.": "Узимање података о каналу није успело.", + "Could not get channel info.": "Није могуће прикупити информације о каналу.", "search_filters_features_option_hd": "HD", "Slovenian": "Словеначки", "Load more": "Учитај више", @@ -276,53 +276,53 @@ "Luxembourgish": "Луксембуршки", "Mongolian": "Монголски", "Latvian": "Летонски", - "channel:`x`": "kanal:`x`", + "channel:`x`": "канал:`x`", "Southern Sotho": "Јужни Сото", "Popular": "Популарно", "Gujarati": "Гуџарати", "search_filters_date_option_year": "Ове године", "Irish": "Ирски", - "YouTube comment permalink": "YouTube коментар трајна веза", + "YouTube comment permalink": "Трајни линк YouTube коментара", "Malagasy": "Малгашки", - "Token is expired, please try again": "Жетон је истекао, молимо вас да покушате поново", - "search_filters_duration_option_short": "Кратко (< 4 минуте)", + "Token is expired, please try again": "Токен је истекао, покушајте поново", + "search_filters_duration_option_short": "Кратко (< 4 минута)", "Samoan": "Самоански", "Tamil": "Тамилски", "Ukrainian": "Украјински", - "permalink": "трајна веза", + "permalink": "трајни линк", "Pashto": "Паштунски", "channel_tab_community_label": "Заједница", "Sindhi": "Синди", - "Could not fetch comments": "Узимање коментара није успело", - "Bangla": "Бангла/Бенгалски", + "Could not fetch comments": "Није могуће прикупити коментаре", + "Bangla": "Бенгалски", "Uzbek": "Узбечки", "Lithuanian": "Литвански", "Icelandic": "Исландски", "Thai": "Тајски", - "search_filters_date_option_month": "Овај месец", - "search_filters_type_label": "Тип", + "search_filters_date_option_month": "Овог месеца", + "search_filters_type_label": "Врста", "search_filters_date_option_hour": "Последњи сат", "Spanish": "Шпански", "search_filters_sort_option_date": "Датум отпремања", - "View as playlist": "Погледај као плеј листу", + "View as playlist": "Погледај као плејлисту", "search_filters_sort_option_relevance": "Релевантност", "Estonian": "Естонски", - "Sinhala": "Синхалешки", + "Sinhala": "Синхалски", "Corsican": "Корзикански", - "Filipino": "Филипино", - "Gaming": "Игрице", + "Filipino": "Филипински", + "Gaming": "Видео игре", "Movies": "Филмови", - "search_filters_sort_option_rating": "Оцене", - "Top enabled: ": "Врх омогућен: ", - "Released under the AGPLv3 on Github.": "Избачено под лиценцом AGPLv3 на GitHub-у.", + "search_filters_sort_option_rating": "Оцена", + "Top enabled: ": "Топ омогућено: ", + "Released under the AGPLv3 on Github.": "Објављено под лиценцом AGPLv3 на GitHub-у.", "Afrikaans": "Африканс", - "preferences_automatic_instance_redirect_label": "Аутоматско пребацивање на другу инстанцу у случају отказивања (пречи ће назад на редирецт.инвидиоус.ио): ", - "Please log in": "Молимо вас да се пријавите", + "preferences_automatic_instance_redirect_label": "Аутоматско преусмеравање инстанце (повратак на redirect.invidious.io): ", + "Please log in": "Молимо, пријавите се", "English (auto-generated)": "Енглески (аутоматски генерисано)", "Hindi": "Хинди", - "Italian": "Талијански", - "Malayalam": "Малајалам", - "Punjabi": "Пунџаби", + "Italian": "Италијански", + "Malayalam": "Малајаламски", + "Punjabi": "Панџапски", "Somali": "Сомалијски", "Vietnamese": "Вијетнамски", "Welsh": "Велшки", @@ -330,25 +330,25 @@ "Maltese": "Малтешки", "Swedish": "Шведски", "Music": "Музика", - "Download as: ": "Преузми као: ", + "Download as: ": "Преузети као: ", "search_filters_duration_label": "Трајање", - "search_filters_sort_label": "Поредај према", - "search_filters_features_option_subtitles": "Титл/Превод", - "preferences_extend_desc_label": "Аутоматски прикажи цео опис видеа: ", + "search_filters_sort_label": "Сортирање по", + "search_filters_features_option_subtitles": "Титлови/Скривени титлови", + "preferences_extend_desc_label": "Аутоматски прошири опис видео снимка: ", "Show less": "Прикажи мање", "Family friendly? ": "Погодно за породицу? ", - "Premieres `x`": "Премерe у `x`", + "Premieres `x`": "Премијера `x`", "Bosnian": "Босански", "Catalan": "Каталонски", "Japanese": "Јапански", "Latin": "Латински", - "next_steps_error_message_refresh": "Освежи страницу", - "footer_original_source_code": "Оригинална Изворна Кода", + "next_steps_error_message_refresh": "Освежите", + "footer_original_source_code": "Оригинални изворни кôд", "Romanian": "Румунски", "Serbian": "Српски", - "Top": "Врх", - "Video mode": "Видео мод", - "footer_source_code": "Изворна Кода", + "Top": "Топ", + "Video mode": "Режим видео снимка", + "footer_source_code": "Изворни кôд", "search_filters_features_option_three_d": "3D", "search_filters_features_option_four_k": "4K", "Erroneous CAPTCHA": "Погрешна CAPTCHA", @@ -360,5 +360,148 @@ "Korean": "Корејски", "Kurdish": "Курдски", "Malay": "Малајски", - "search_filters_title": "Филтер" + "search_filters_title": "Филтери", + "Korean (auto-generated)": "Корејски (аутоматски генерисано)", + "search_filters_features_option_three_sixty": "360°", + "preferences_quality_dash_option_worst": "Најгоре", + "channel_tab_podcasts_label": "Подкасти", + "preferences_save_player_pos_label": "Сачувај позицију репродукције: ", + "Spanish (Mexico)": "Шпански (Мексико)", + "generic_subscriptions_count_0": "{{count}} праћење", + "generic_subscriptions_count_1": "{{count}} праћења", + "generic_subscriptions_count_2": "{{count}} праћења", + "search_filters_apply_button": "Примени изабране филтере", + "Download is disabled": "Преузимање је онемогућено", + "comments_points_count_0": "{{count}} поен", + "comments_points_count_1": "{{count}} поена", + "comments_points_count_2": "{{count}} поена", + "preferences_quality_dash_option_2160p": "2160p", + "German (auto-generated)": "Немачки (аутоматски генерисано)", + "Japanese (auto-generated)": "Јапански (аутоматски генерисано)", + "preferences_quality_option_medium": "Средње", + "search_message_change_filters_or_query": "Покушајте да проширите упит за претрагу и/или промените филтере.", + "crash_page_before_reporting": "Пре него што пријавите грешку, уверите се да сте:", + "preferences_quality_dash_option_best": "Најбоље", + "Channel Sponsor": "Спонзор канала", + "generic_videos_count_0": "{{count}} видео снимак", + "generic_videos_count_1": "{{count}} видео снимка", + "generic_videos_count_2": "{{count}} видео снимака", + "videoinfo_started_streaming_x_ago": "Започето стримовање пре `x`", + "videoinfo_youTube_embed_link": "Уграђено", + "channel_tab_streams_label": "Стримови уживо", + "playlist_button_add_items": "Додај видео снимке", + "generic_count_minutes_0": "{{count}} минут", + "generic_count_minutes_1": "{{count}} минута", + "generic_count_minutes_2": "{{count}} минута", + "preferences_quality_dash_option_720p": "720p", + "preferences_watch_history_label": "Омогући историју гледања: ", + "user_saved_playlists": "Сачуваних плејлиста: `x`", + "Spanish (Spain)": "Шпански (Шпанија)", + "invidious": "Invidious", + "crash_page_refresh": "покушали да освежите страницу", + "Chinese (Hong Kong)": "Кинески (Хонг Конг)", + "Artist: ": "Извођач: ", + "generic_count_months_0": "{{count}} месец", + "generic_count_months_1": "{{count}} месеца", + "generic_count_months_2": "{{count}} месеци", + "search_message_use_another_instance": " Такође, можете претраживати на другој инстанци.", + "generic_subscribers_count_0": "{{count}} пратилац", + "generic_subscribers_count_1": "{{count}} пратиоца", + "generic_subscribers_count_2": "{{count}} пратилаца", + "download_subtitles": "Титлови - `x` (.vtt)", + "generic_button_save": "Сачувај", + "crash_page_search_issue": "претражили постојеће извештаје о проблемима на GitHub-у", + "generic_button_cancel": "Откажи", + "none": "ниједно", + "English (United States)": "Енглески (Сједињене Америчке Државе)", + "subscriptions_unseen_notifs_count_0": "{{count}} невиђено обавештење", + "subscriptions_unseen_notifs_count_1": "{{count}} невиђена обавештења", + "subscriptions_unseen_notifs_count_2": "{{count}} невиђених обавештења", + "Album: ": "Албум: ", + "preferences_quality_option_dash": "DASH (адаптивни квалитет)", + "preferences_quality_dash_option_1080p": "1080p", + "Video unavailable": "Видео снимак недоступан", + "tokens_count_0": "{{count}} токен", + "tokens_count_1": "{{count}} токена", + "tokens_count_2": "{{count}} токена", + "Chinese (China)": "Кинески (Кина)", + "Italian (auto-generated)": "Италијански (аутоматски генерисано)", + "channel_tab_shorts_label": "Shorts", + "preferences_quality_dash_option_1440p": "1440p", + "preferences_quality_dash_option_360p": "360p", + "search_message_no_results": "Нису пронађени резултати.", + "channel_tab_releases_label": "Издања", + "preferences_quality_dash_option_144p": "144p", + "Interlingue": "Интерлингва", + "Song: ": "Песма: ", + "generic_channels_count_0": "{{count}} канал", + "generic_channels_count_1": "{{count}} канала", + "generic_channels_count_2": "{{count}} канала", + "Chinese (Taiwan)": "Кинески (Тајван)", + "Turkish (auto-generated)": "Турски (аутоматски генерисано)", + "Indonesian (auto-generated)": "Индонезијски (аутоматски генерисано)", + "Portuguese (auto-generated)": "Португалски (аутоматски генерисано)", + "generic_count_years_0": "{{count}} година", + "generic_count_years_1": "{{count}} године", + "generic_count_years_2": "{{count}} година", + "videoinfo_invidious_embed_link": "Уграђени линк", + "Popular enabled: ": "Популарно омогућено: ", + "Spanish (auto-generated)": "Шпански (аутоматски генерисано)", + "preferences_quality_option_small": "Мало", + "English (United Kingdom)": "Енглески (Уједињено Краљевство)", + "channel_tab_playlists_label": "Плејлисте", + "generic_button_edit": "Измени", + "generic_playlists_count_0": "{{count}} плејлиста", + "generic_playlists_count_1": "{{count}} плејлисте", + "generic_playlists_count_2": "{{count}} плејлиста", + "preferences_quality_option_hd720": "HD720", + "search_filters_features_option_purchased": "Купљено", + "search_filters_date_option_none": "Било који датум", + "preferences_quality_dash_option_auto": "Аутоматски", + "Cantonese (Hong Kong)": "Кантонски (Хонг Конг)", + "crash_page_report_issue": "Ако ништа од горенаведеног није помогло, отворите нови извештај о проблему на GitHub-у (по могућности на енглеском) и укључите следећи текст у своју поруку (НЕ преводите тај текст):", + "crash_page_switch_instance": "покушали да користите другу инстанцу", + "generic_count_weeks_0": "{{count}} недеља", + "generic_count_weeks_1": "{{count}} недеље", + "generic_count_weeks_2": "{{count}} недеља", + "videoinfo_watch_on_youTube": "Гледај на YouTube-у", + "Music in this video": "Музика у овом видео снимку", + "generic_button_rss": "RSS", + "preferences_quality_dash_option_4320p": "4320p", + "generic_count_hours_0": "{{count}} сат", + "generic_count_hours_1": "{{count}} сата", + "generic_count_hours_2": "{{count}} сати", + "French (auto-generated)": "Француски (аутоматски генерисано)", + "crash_page_read_the_faq": "прочитали Често Постављана Питања (ЧПП)", + "user_created_playlists": "Направљених плејлиста: `x`", + "channel_tab_channels_label": "Канали", + "search_filters_type_option_all": "Било која врста", + "Russian (auto-generated)": "Руски (аутоматски генерисано)", + "preferences_quality_dash_option_480p": "480p", + "comments_view_x_replies_0": "Погледај {{count}} одговор", + "comments_view_x_replies_1": "Погледај {{count}} одговора", + "comments_view_x_replies_2": "Погледај {{count}} одговора", + "Portuguese (Brazil)": "Португалски (Бразил)", + "search_filters_features_option_vr180": "VR180", + "error_video_not_in_playlist": "Тражени видео снимак не постоји на овој плејлисти. Кликните овде за почетну страницу плејлисте.", + "Dutch (auto-generated)": "Холандски (аутоматски генерисано)", + "generic_count_days_0": "{{count}} дан", + "generic_count_days_1": "{{count}} дана", + "generic_count_days_2": "{{count}} дана", + "Vietnamese (auto-generated)": "Вијетнамски (аутоматски генерисано)", + "search_filters_duration_option_none": "Било које трајање", + "preferences_quality_dash_option_240p": "240p", + "Chinese": "Кинески", + "generic_button_delete": "Избриши", + "Import YouTube playlist (.csv)": "Увези YouTube плејлисту (.csv)", + "Standard YouTube license": "Стандардна YouTube лиценца", + "search_filters_duration_option_medium": "Средње (4 - 20 минута)", + "generic_count_seconds_0": "{{count}} секунда", + "generic_count_seconds_1": "{{count}} секунде", + "generic_count_seconds_2": "{{count}} секунди", + "search_filters_date_label": "Датум отпремања", + "crash_page_you_found_a_bug": "Изгледа да сте пронашли грешку у Invidious-у!", + "generic_views_count_0": "{{count}} преглед", + "generic_views_count_1": "{{count}} прегледа", + "generic_views_count_2": "{{count}} прегледа" } diff --git a/locales/tr.json b/locales/tr.json index 7f3f2de8..0575a4dd 100644 --- a/locales/tr.json +++ b/locales/tr.json @@ -484,5 +484,7 @@ "generic_button_rss": "RSS", "channel_tab_releases_label": "Yayınlar", "playlist_button_add_items": "Video ekle", - "channel_tab_podcasts_label": "Podcast'ler" + "channel_tab_podcasts_label": "Podcast'ler", + "generic_channels_count": "{{count}} kanal", + "generic_channels_count_plural": "{{count}} kanal" } diff --git a/locales/uk.json b/locales/uk.json index 4d8f06a5..c26618fe 100644 --- a/locales/uk.json +++ b/locales/uk.json @@ -500,5 +500,8 @@ "channel_tab_releases_label": "Випуски", "generic_button_delete": "Видалити", "generic_button_edit": "Змінити", - "generic_button_save": "Зберегти" + "generic_button_save": "Зберегти", + "generic_channels_count_0": "{{count}} канал", + "generic_channels_count_1": "{{count}} канали", + "generic_channels_count_2": "{{count}} каналів" } diff --git a/locales/zh-CN.json b/locales/zh-CN.json index 62f45a29..5e5d0ebb 100644 --- a/locales/zh-CN.json +++ b/locales/zh-CN.json @@ -468,5 +468,6 @@ "generic_button_edit": "编辑", "generic_button_save": "保存", "generic_button_rss": "RSS", - "channel_tab_releases_label": "公告" + "channel_tab_releases_label": "公告", + "generic_channels_count_0": "{{count}} 个频道" } diff --git a/locales/zh-TW.json b/locales/zh-TW.json index da81922b..de659c92 100644 --- a/locales/zh-TW.json +++ b/locales/zh-TW.json @@ -468,5 +468,6 @@ "generic_button_delete": "刪除", "playlist_button_add_items": "新增影片", "channel_tab_podcasts_label": "Podcast", - "channel_tab_releases_label": "發布" + "channel_tab_releases_label": "發布", + "generic_channels_count_0": "{{count}} 個頻道" } diff --git a/spec/helpers/vtt/builder_spec.cr b/spec/helpers/vtt/builder_spec.cr new file mode 100644 index 00000000..7b543ddc --- /dev/null +++ b/spec/helpers/vtt/builder_spec.cr @@ -0,0 +1,64 @@ +require "../../spec_helper.cr" + +MockLines = [ + { + "start_time": Time::Span.new(seconds: 1), + "end_time": Time::Span.new(seconds: 2), + "text": "Line 1", + }, + + { + "start_time": Time::Span.new(seconds: 2), + "end_time": Time::Span.new(seconds: 3), + "text": "Line 2", + }, +] + +Spectator.describe "WebVTT::Builder" do + it "correctly builds a vtt file" do + result = WebVTT.build do |vtt| + MockLines.each do |line| + vtt.cue(line["start_time"], line["end_time"], line["text"]) + end + end + + expect(result).to eq([ + "WEBVTT", + "", + "00:00:01.000 --> 00:00:02.000", + "Line 1", + "", + "00:00:02.000 --> 00:00:03.000", + "Line 2", + "", + "", + ].join('\n')) + end + + it "correctly builds a vtt file with setting fields" do + setting_fields = { + "Kind" => "captions", + "Language" => "en", + } + + result = WebVTT.build(setting_fields) do |vtt| + MockLines.each do |line| + vtt.cue(line["start_time"], line["end_time"], line["text"]) + end + end + + expect(result).to eq([ + "WEBVTT", + "Kind: captions", + "Language: en", + "", + "00:00:01.000 --> 00:00:02.000", + "Line 1", + "", + "00:00:02.000 --> 00:00:03.000", + "Line 2", + "", + "", + ].join('\n')) + end +end diff --git a/spec/i18next_plurals_spec.cr b/spec/i18next_plurals_spec.cr index ee9ff394..dab97710 100644 --- a/spec/i18next_plurals_spec.cr +++ b/spec/i18next_plurals_spec.cr @@ -15,12 +15,15 @@ FORM_TESTS = { "ar" => I18next::Plurals::PluralForms::Special_Arabic, "be" => I18next::Plurals::PluralForms::Dual_Slavic, "cy" => I18next::Plurals::PluralForms::Special_Welsh, + "fr" => I18next::Plurals::PluralForms::Special_French_Portuguese, "en" => I18next::Plurals::PluralForms::Single_not_one, - "fr" => I18next::Plurals::PluralForms::Single_gt_one, + "es" => I18next::Plurals::PluralForms::Single_not_one, "ga" => I18next::Plurals::PluralForms::Special_Irish, "gd" => I18next::Plurals::PluralForms::Special_Scottish_Gaelic, "he" => I18next::Plurals::PluralForms::Special_Hebrew, + "hr" => I18next::Plurals::PluralForms::Special_Hungarian_Serbian, "is" => I18next::Plurals::PluralForms::Special_Icelandic, + "it" => I18next::Plurals::PluralForms::Special_Spanish_Italian, "jv" => I18next::Plurals::PluralForms::Special_Javanese, "kw" => I18next::Plurals::PluralForms::Special_Cornish, "lt" => I18next::Plurals::PluralForms::Special_Lithuanian, @@ -31,12 +34,12 @@ FORM_TESTS = { "or" => I18next::Plurals::PluralForms::Special_Odia, "pl" => I18next::Plurals::PluralForms::Special_Polish_Kashubian, "pt" => I18next::Plurals::PluralForms::Single_gt_one, - "pt-PT" => I18next::Plurals::PluralForms::Single_not_one, - "pt-BR" => I18next::Plurals::PluralForms::Single_gt_one, + "pt-BR" => I18next::Plurals::PluralForms::Special_French_Portuguese, "ro" => I18next::Plurals::PluralForms::Special_Romanian, - "su" => I18next::Plurals::PluralForms::None, "sk" => I18next::Plurals::PluralForms::Special_Czech_Slovak, "sl" => I18next::Plurals::PluralForms::Special_Slovenian, + "su" => I18next::Plurals::PluralForms::None, + "sr" => I18next::Plurals::PluralForms::Special_Hungarian_Serbian, } SUFFIX_TESTS = { @@ -73,10 +76,18 @@ SUFFIX_TESTS = { {num: 1, suffix: ""}, {num: 10, suffix: "_plural"}, ], - "fr" => [ - {num: 0, suffix: ""}, + "es" => [ + {num: 0, suffix: "_plural"}, {num: 1, suffix: ""}, {num: 10, suffix: "_plural"}, + {num: 6_000_000, suffix: "_plural"}, + ], + "fr" => [ + {num: 0, suffix: "_0"}, + {num: 1, suffix: "_0"}, + {num: 10, suffix: "_2"}, + {num: 4_000_000, suffix: "_1"}, + {num: 6_260_000, suffix: "_2"}, ], "ga" => [ {num: 1, suffix: "_0"}, @@ -155,31 +166,24 @@ SUFFIX_TESTS = { {num: 1, suffix: "_0"}, {num: 5, suffix: "_2"}, ], - "pt" => [ - {num: 0, suffix: ""}, - {num: 1, suffix: ""}, - {num: 10, suffix: "_plural"}, + "pt-BR" => [ + {num: 0, suffix: "_0"}, + {num: 1, suffix: "_0"}, + {num: 10, suffix: "_2"}, + {num: 42, suffix: "_2"}, + {num: 9_000_000, suffix: "_1"}, ], "pt-PT" => [ - {num: 0, suffix: "_plural"}, - {num: 1, suffix: ""}, - {num: 10, suffix: "_plural"}, - ], - "pt-BR" => [ {num: 0, suffix: ""}, {num: 1, suffix: ""}, {num: 10, suffix: "_plural"}, + {num: 9_000_000, suffix: "_plural"}, ], "ro" => [ {num: 0, suffix: "_1"}, {num: 1, suffix: "_0"}, {num: 20, suffix: "_2"}, ], - "su" => [ - {num: 0, suffix: "_0"}, - {num: 1, suffix: "_0"}, - {num: 10, suffix: "_0"}, - ], "sk" => [ {num: 0, suffix: "_2"}, {num: 1, suffix: "_0"}, @@ -191,6 +195,18 @@ SUFFIX_TESTS = { {num: 2, suffix: "_2"}, {num: 3, suffix: "_3"}, ], + "su" => [ + {num: 0, suffix: "_0"}, + {num: 1, suffix: "_0"}, + {num: 10, suffix: "_0"}, + ], + "sr" => [ + {num: 1, suffix: "_0"}, + {num: 51, suffix: "_0"}, + {num: 32, suffix: "_1"}, + {num: 100, suffix: "_2"}, + {num: 100_000, suffix: "_2"}, + ], } Spectator.describe "i18next_Plural_Resolver" do diff --git a/src/invidious/channels/community.cr b/src/invidious/channels/community.cr index 791f1641..49ffd990 100644 --- a/src/invidious/channels/community.cr +++ b/src/invidious/channels/community.cr @@ -24,7 +24,33 @@ def fetch_channel_community(ucid, cursor, locale, format, thin_mode) return extract_channel_community(items, ucid: ucid, locale: locale, format: format, thin_mode: thin_mode) end -def extract_channel_community(items, *, ucid, locale, format, thin_mode) +def fetch_channel_community_post(ucid, post_id, locale, format, thin_mode) + object = { + "2:string" => "community", + "25:embedded" => { + "22:string" => post_id.to_s, + }, + "45:embedded" => { + "2:varint" => 1_i64, + "3:varint" => 1_i64, + }, + } + 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) } + + initial_data = YoutubeAPI.browse(ucid, params: params) + + items = [] of JSON::Any + extract_items(initial_data) do |item| + items << item + end + + return extract_channel_community(items, ucid: ucid, locale: locale, format: format, thin_mode: thin_mode, is_single_post: true) +end + +def extract_channel_community(items, *, ucid, locale, format, thin_mode, is_single_post : Bool = false) if message = items[0]["messageRenderer"]? error_message = (message["text"]["simpleText"]? || message["text"]["runs"]?.try &.[0]?.try &.["text"]?) @@ -39,6 +65,9 @@ def extract_channel_community(items, *, ucid, locale, format, thin_mode) response = JSON.build do |json| json.object do json.field "authorId", ucid + if is_single_post + json.field "singlePost", true + end json.field "comments" do json.array do items.each do |post| @@ -240,8 +269,10 @@ def extract_channel_community(items, *, ucid, locale, format, thin_mode) end end end - if cont = items.dig?(-1, "continuationItemRenderer", "continuationEndpoint", "continuationCommand", "token") - json.field "continuation", extract_channel_community_cursor(cont.as_s) + if !is_single_post + if cont = items.dig?(-1, "continuationItemRenderer", "continuationEndpoint", "continuationCommand", "token") + json.field "continuation", extract_channel_community_cursor(cont.as_s) + end end end end diff --git a/src/invidious/comments/youtube.cr b/src/invidious/comments/youtube.cr index 1ba1b534..185d8e43 100644 --- a/src/invidious/comments/youtube.cr +++ b/src/invidious/comments/youtube.cr @@ -13,6 +13,51 @@ module Invidious::Comments client_config = YoutubeAPI::ClientConfig.new(region: region) response = YoutubeAPI.next(continuation: ctoken, client_config: client_config) + return parse_youtube(id, response, format, locale, thin_mode, sort_by) + end + + def fetch_community_post_comments(ucid, post_id) + object = { + "2:string" => "community", + "25:embedded" => { + "22:string" => post_id, + }, + "45:embedded" => { + "2:varint" => 1_i64, + "3:varint" => 1_i64, + }, + "53:embedded" => { + "4:embedded" => { + "6:varint" => 0_i64, + "27:varint" => 1_i64, + "29:string" => post_id, + "30:string" => ucid, + }, + "8:string" => "comments-section", + }, + } + + object_parsed = object.try { |i| Protodec::Any.cast_json(i) } + .try { |i| Protodec::Any.from_json(i) } + .try { |i| Base64.urlsafe_encode(i) } + + object2 = { + "80226972:embedded" => { + "2:string" => ucid, + "3:string" => object_parsed, + }, + } + + continuation = object2.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) } + + initial_data = YoutubeAPI.browse(continuation: continuation) + return initial_data + end + + def parse_youtube(id, response, format, locale, thin_mode, sort_by = "top", isPost = false) contents = nil if on_response_received_endpoints = response["onResponseReceivedEndpoints"]? @@ -68,7 +113,11 @@ module Invidious::Comments json.field "commentCount", comment_count end - json.field "videoId", id + if isPost + json.field "postId", id + else + json.field "videoId", id + end json.field "comments" do json.array do diff --git a/src/invidious/frontend/comments_youtube.cr b/src/invidious/frontend/comments_youtube.cr index 41f43f04..ecc0bc1b 100644 --- a/src/invidious/frontend/comments_youtube.cr +++ b/src/invidious/frontend/comments_youtube.cr @@ -23,6 +23,24 @@ module Invidious::Frontend::Comments END_HTML + elsif comments["authorId"]? && !comments["singlePost"]? + # for posts we should display a link to the post + replies_count_text = translate_count(locale, + "comments_view_x_replies", + child["replyCount"].as_i64 || 0, + NumberFormatting::Separator + ) + + replies_html = <<-END_HTML +
+
+
+

+ #{replies_count_text} +

+
+
+ END_HTML end if !thin_mode diff --git a/src/invidious/helpers/i18next.cr b/src/invidious/helpers/i18next.cr index e84f88fb..252af6b9 100644 --- a/src/invidious/helpers/i18next.cr +++ b/src/invidious/helpers/i18next.cr @@ -35,19 +35,27 @@ module I18next::Plurals Special_Slovenian = 21 Special_Hebrew = 22 Special_Odia = 23 + + # Mixed v3/v4 rules in Weblate + # `es`, `pt` and `pt-PT` doesn't seem to have been refreshed + # by weblate yet, but I suspect it will happen one day. + # See: https://github.com/translate/translate/issues/4873 + Special_French_Portuguese + Special_Hungarian_Serbian + Special_Spanish_Italian end private PLURAL_SETS = { PluralForms::Single_gt_one => [ - "ach", "ak", "am", "arn", "br", "fil", "fr", "gun", "ln", "mfe", "mg", - "mi", "oc", "pt", "pt-BR", "tg", "tl", "ti", "tr", "uz", "wa", + "ach", "ak", "am", "arn", "br", "fil", "gun", "ln", "mfe", "mg", + "mi", "oc", "pt", "tg", "tl", "ti", "tr", "uz", "wa", ], PluralForms::Single_not_one => [ "af", "an", "ast", "az", "bg", "bn", "ca", "da", "de", "dev", "el", "en", "eo", "es", "et", "eu", "fi", "fo", "fur", "fy", "gl", "gu", "ha", "hi", - "hu", "hy", "ia", "it", "kk", "kn", "ku", "lb", "mai", "ml", "mn", "mr", + "hu", "hy", "ia", "kk", "kn", "ku", "lb", "mai", "ml", "mn", "mr", "nah", "nap", "nb", "ne", "nl", "nn", "no", "nso", "pa", "pap", "pms", - "ps", "pt-PT", "rm", "sco", "se", "si", "so", "son", "sq", "sv", "sw", + "ps", "rm", "sco", "se", "si", "so", "son", "sq", "sv", "sw", "ta", "te", "tk", "ur", "yo", ], PluralForms::None => [ @@ -55,7 +63,7 @@ module I18next::Plurals "lo", "ms", "sah", "su", "th", "tt", "ug", "vi", "wo", "zh", ], PluralForms::Dual_Slavic => [ - "be", "bs", "cnr", "dz", "hr", "ru", "sr", "uk", + "be", "bs", "cnr", "dz", "ru", "uk", ], } @@ -81,6 +89,12 @@ module I18next::Plurals "ro" => PluralForms::Special_Romanian, "sk" => PluralForms::Special_Czech_Slovak, "sl" => PluralForms::Special_Slovenian, + # Mixed v3/v4 rules + "fr" => PluralForms::Special_French_Portuguese, + "hr" => PluralForms::Special_Hungarian_Serbian, + "it" => PluralForms::Special_Spanish_Italian, + "pt-BR" => PluralForms::Special_French_Portuguese, + "sr" => PluralForms::Special_Hungarian_Serbian, } # These are the v1 and v2 compatible suffixes. @@ -150,9 +164,8 @@ module I18next::Plurals end def get_plural_form(locale : String) : PluralForms - # Extract the ISO 639-1 or 639-2 code from an RFC 5646 language code, - # except for pt-BR and pt-PT which needs to be kept as-is. - if !locale.matches?(/^pt-(BR|PT)$/) + # Extract the ISO 639-1 or 639-2 code from an RFC 5646 language code + if !locale.matches?(/^pt-BR$/) locale = locale.split('-')[0] end @@ -246,6 +259,10 @@ module I18next::Plurals when .special_slovenian? then return special_slovenian(count) when .special_hebrew? then return special_hebrew(count) when .special_odia? then return special_odia(count) + # Mixed v3/v4 forms + when .special_spanish_italian? then return special_cldr_Spanish_Italian(count) + when .special_french_portuguese? then return special_cldr_French_Portuguese(count) + when .special_hungarian_serbian? then return special_cldr_Hungarian_Serbian(count) else # default, if nothing matched above return 0_u8 @@ -507,5 +524,42 @@ module I18next::Plurals def self.special_odia(count : Int) : UInt8 return (count == 1) ? 0_u8 : 1_u8 end + + # ------------------- + # "v3.5" rules + # ------------------- + + # Plural form for Spanish & Italian languages + # + # This rule is mostly compliant to CLDR v42 + # + def self.special_cldr_Spanish_Italian(count : Int) : UInt8 + return 0_u8 if (count == 1) # one + return 1_u8 if (count != 0 && count % 1_000_000 == 0) # many + return 2_u8 # other + end + + # Plural form for French and Portuguese + # + # This rule is mostly compliant to CLDR v42 + # + def self.special_cldr_French_Portuguese(count : Int) : UInt8 + return 0_u8 if (count == 0 || count == 1) # one + return 1_u8 if (count % 1_000_000 == 0) # many + return 2_u8 # other + end + + # Plural form for Hungarian and Serbian + # + # This rule is mostly compliant to CLDR v42 + # + def self.special_cldr_Hungarian_Serbian(count : Int) : UInt8 + n_mod_10 = count % 10 + n_mod_100 = count % 100 + + return 0_u8 if (n_mod_10 == 1 && n_mod_100 != 11) # one + return 1_u8 if (2 <= n_mod_10 <= 4 && (n_mod_100 < 12 || 14 < n_mod_100)) # few + return 2_u8 # other + end end end diff --git a/src/invidious/helpers/serialized_yt_data.cr b/src/invidious/helpers/serialized_yt_data.cr index e0bd7279..31a3cf44 100644 --- a/src/invidious/helpers/serialized_yt_data.cr +++ b/src/invidious/helpers/serialized_yt_data.cr @@ -186,6 +186,7 @@ struct SearchChannel property author_thumbnail : String property subscriber_count : Int32 property video_count : Int32 + property channel_handle : String? property description_html : String property auto_generated : Bool property author_verified : Bool @@ -214,6 +215,7 @@ struct SearchChannel json.field "autoGenerated", self.auto_generated json.field "subCount", self.subscriber_count json.field "videoCount", self.video_count + json.field "channelHandle", self.channel_handle json.field "description", html_to_content(self.description_html) json.field "descriptionHtml", self.description_html diff --git a/src/invidious/helpers/webvtt.cr b/src/invidious/helpers/webvtt.cr new file mode 100644 index 00000000..56f761ed --- /dev/null +++ b/src/invidious/helpers/webvtt.cr @@ -0,0 +1,67 @@ +# Namespace for logic relating to generating WebVTT files +# +# Probably not compliant to WebVTT's specs but it is enough for Invidious. +module WebVTT + # A WebVTT builder generates WebVTT files + private class Builder + def initialize(@io : IO) + end + + # Writes an vtt cue with the specified time stamp and contents + def cue(start_time : Time::Span, end_time : Time::Span, text : String) + timestamp(start_time, end_time) + @io << text + @io << "\n\n" + end + + private def timestamp(start_time : Time::Span, end_time : Time::Span) + timestamp_component(start_time) + @io << " --> " + timestamp_component(end_time) + + @io << '\n' + end + + private def timestamp_component(timestamp : Time::Span) + @io << timestamp.hours.to_s.rjust(2, '0') + @io << ':' << timestamp.minutes.to_s.rjust(2, '0') + @io << ':' << timestamp.seconds.to_s.rjust(2, '0') + @io << '.' << timestamp.milliseconds.to_s.rjust(3, '0') + end + + def document(setting_fields : Hash(String, String)? = nil, &) + @io << "WEBVTT\n" + + if setting_fields + setting_fields.each do |name, value| + @io << name << ": " << value << '\n' + end + end + + @io << '\n' + + yield + end + end + + # Returns the resulting `String` of writing WebVTT to the yielded `WebVTT::Builder` + # + # ``` + # string = WebVTT.build do |vtt| + # vtt.cue(Time::Span.new(seconds: 1), Time::Span.new(seconds: 2), "Line 1") + # vtt.cue(Time::Span.new(seconds: 2), Time::Span.new(seconds: 3), "Line 2") + # end + # + # string # => "WEBVTT\n\n00:00:01.000 --> 00:00:02.000\nLine 1\n\n00:00:02.000 --> 00:00:03.000\nLine 2\n\n" + # ``` + # + # Accepts an optional settings fields hash to add settings attribute to the resulting vtt file. + def self.build(setting_fields : Hash(String, String)? = nil, &) + String.build do |str| + builder = Builder.new(str) + builder.document(setting_fields) do + yield builder + end + end + end +end diff --git a/src/invidious/routes/api/v1/channels.cr b/src/invidious/routes/api/v1/channels.cr index adf05d30..67018660 100644 --- a/src/invidious/routes/api/v1/channels.cr +++ b/src/invidious/routes/api/v1/channels.cr @@ -343,6 +343,59 @@ module Invidious::Routes::API::V1::Channels end end + def self.post(env) + locale = env.get("preferences").as(Preferences).locale + + env.response.content_type = "application/json" + id = env.params.url["id"].to_s + ucid = env.params.query["ucid"]? + + thin_mode = env.params.query["thin_mode"]? + thin_mode = thin_mode == "true" + + format = env.params.query["format"]? + format ||= "json" + + if ucid.nil? + response = YoutubeAPI.resolve_url("https://www.youtube.com/post/#{id}") + return error_json(400, "Invalid post ID") if response["error"]? + ucid = response.dig("endpoint", "browseEndpoint", "browseId").as_s + else + ucid = ucid.to_s + end + + begin + fetch_channel_community_post(ucid, id, locale, format, thin_mode) + rescue ex + return error_json(500, ex) + end + end + + def self.post_comments(env) + locale = env.get("preferences").as(Preferences).locale + + env.response.content_type = "application/json" + + id = env.params.url["id"] + + thin_mode = env.params.query["thin_mode"]? + thin_mode = thin_mode == "true" + + format = env.params.query["format"]? + format ||= "json" + + continuation = env.params.query["continuation"]? + + case continuation + when nil, "" + ucid = env.params.query["ucid"] + comments = Comments.fetch_community_post_comments(ucid, id) + else + comments = YoutubeAPI.browse(continuation: continuation) + end + return Comments.parse_youtube(id, comments, format, locale, thin_mode, isPost: true) + end + def self.channels(env) locale = env.get("preferences").as(Preferences).locale ucid = env.params.url["ucid"] diff --git a/src/invidious/routes/api/v1/misc.cr b/src/invidious/routes/api/v1/misc.cr index 4fdb7074..701b22d4 100644 --- a/src/invidious/routes/api/v1/misc.cr +++ b/src/invidious/routes/api/v1/misc.cr @@ -228,17 +228,20 @@ module Invidious::Routes::API::V1::Misc resolved_url = YoutubeAPI.resolve_url(url.as(String)) endpoint = resolved_url["endpoint"] pageType = endpoint.dig?("commandMetadata", "webCommandMetadata", "webPageType").try &.as_s || "" - if resolved_ucid = endpoint.dig?("watchEndpoint", "videoId") - elsif resolved_ucid = endpoint.dig?("browseEndpoint", "browseId") - elsif pageType == "WEB_PAGE_TYPE_UNKNOWN" + if pageType == "WEB_PAGE_TYPE_UNKNOWN" return error_json(400, "Unknown url") end + + sub_endpoint = endpoint["watchEndpoint"]? || endpoint["browseEndpoint"]? || endpoint + params = sub_endpoint.try &.dig?("params") rescue ex return error_json(500, ex) end JSON.build do |json| json.object do - json.field "ucid", resolved_ucid.try &.as_s || "" + json.field "ucid", sub_endpoint["browseId"].as_s if sub_endpoint["browseId"]? + json.field "videoId", sub_endpoint["videoId"].as_s if sub_endpoint["videoId"]? + json.field "params", params.try &.as_s json.field "pageType", pageType end end diff --git a/src/invidious/routes/api/v1/videos.cr b/src/invidious/routes/api/v1/videos.cr index 25e766d2..449c9f9b 100644 --- a/src/invidious/routes/api/v1/videos.cr +++ b/src/invidious/routes/api/v1/videos.cr @@ -101,20 +101,17 @@ module Invidious::Routes::API::V1::Videos if caption.name.includes? "auto-generated" caption_xml = YT_POOL.client &.get(url).body + settings_field = { + "Kind" => "captions", + "Language" => "#{tlang || caption.language_code}", + } + if caption_xml.starts_with?("/, "") text = text.gsub(/<\/font>/, "") @@ -137,12 +131,7 @@ module Invidious::Routes::API::V1::Videos text = "#{md["text"]}" end - str << <<-END_CUE - #{start_time} --> #{end_time} - #{text} - - - END_CUE + webvtt.cue(start_time, end_time, text) end end end @@ -215,11 +204,7 @@ module Invidious::Routes::API::V1::Videos storyboard = storyboard[0] end - String.build do |str| - str << <<-END_VTT - WEBVTT - END_VTT - + WebVTT.build do |vtt| start_time = 0.milliseconds end_time = storyboard[:interval].milliseconds @@ -231,12 +216,8 @@ module Invidious::Routes::API::V1::Videos storyboard[:storyboard_height].times do |j| storyboard[:storyboard_width].times do |k| - str << <<-END_CUE - #{start_time}.000 --> #{end_time}.000 - #{url}#xywh=#{storyboard[:width] * k},#{storyboard[:height] * j},#{storyboard[:width] - 2},#{storyboard[:height]} - - - END_CUE + current_cue_url = "#{url}#xywh=#{storyboard[:width] * k},#{storyboard[:height] * j},#{storyboard[:width] - 2},#{storyboard[:height]}" + vtt.cue(start_time, end_time, current_cue_url) start_time += storyboard[:interval].milliseconds end_time += storyboard[:interval].milliseconds diff --git a/src/invidious/routes/channels.cr b/src/invidious/routes/channels.cr index 9892ae2a..d4d8b1c1 100644 --- a/src/invidious/routes/channels.cr +++ b/src/invidious/routes/channels.cr @@ -1,6 +1,12 @@ {% skip_file if flag?(:api_only) %} module Invidious::Routes::Channels + # Redirection for unsupported routes ("tabs") + def self.redirect_home(env) + ucid = env.params.url["ucid"] + return env.redirect "/channel/#{URI.encode_www_form(ucid)}" + end + def self.home(env) self.videos(env) end @@ -159,6 +165,11 @@ module Invidious::Routes::Channels end locale, user, subscriptions, continuation, ucid, channel = data + # redirect to post page + if lb = env.params.query["lb"]? + env.redirect "/post/#{URI.encode_www_form(lb)}?ucid=#{URI.encode_www_form(ucid)}" + end + thin_mode = env.params.query["thin_mode"]? || env.get("preferences").as(Preferences).thin_mode thin_mode = thin_mode == "true" @@ -187,6 +198,44 @@ module Invidious::Routes::Channels templated "community" end + def self.post(env) + # /post/{postId} + id = env.params.url["id"] + ucid = env.params.query["ucid"]? + + prefs = env.get("preferences").as(Preferences) + + locale = prefs.locale + + thin_mode = env.params.query["thin_mode"]? || prefs.thin_mode + thin_mode = thin_mode == "true" + + nojs = env.params.query["nojs"]? + + nojs ||= "0" + nojs = nojs == "1" + + if !ucid.nil? + ucid = ucid.to_s + post_response = fetch_channel_community_post(ucid, id, locale, "json", thin_mode) + else + # resolve the url to get the author's UCID + response = YoutubeAPI.resolve_url("https://www.youtube.com/post/#{id}") + return error_template(400, "Invalid post ID") if response["error"]? + + ucid = response.dig("endpoint", "browseEndpoint", "browseId").as_s + post_response = fetch_channel_community_post(ucid, id, locale, "json", thin_mode) + end + + post_response = JSON.parse(post_response) + + if nojs + comments = Comments.fetch_community_post_comments(ucid, id) + comment_html = JSON.parse(Comments.parse_youtube(id, comments, "html", locale, thin_mode, isPost: true))["contentHtml"] + end + templated "post" + end + def self.channels(env) data = self.fetch_basic_information(env) return data if !data.is_a?(Tuple) @@ -217,6 +266,11 @@ module Invidious::Routes::Channels env.redirect "/channel/#{ucid}" end + private KNOWN_TABS = { + "home", "videos", "shorts", "streams", "podcasts", + "releases", "playlists", "community", "channels", "about", + } + # Redirects brand url channels to a normal /channel/:ucid route def self.brand_redirect(env) locale = env.get("preferences").as(Preferences).locale @@ -227,7 +281,10 @@ module Invidious::Routes::Channels yt_url_params = URI::Params.encode(env.params.query.to_h.select(["a", "u", "user"])) # Retrieves URL params that only Invidious uses - invidious_url_params = URI::Params.encode(env.params.query.to_h.select!(["a", "u", "user"])) + invidious_url_params = env.params.query.dup + invidious_url_params.delete_all("a") + invidious_url_params.delete_all("u") + invidious_url_params.delete_all("user") begin resolved_url = YoutubeAPI.resolve_url("https://youtube.com#{env.request.path}#{yt_url_params.size > 0 ? "?#{yt_url_params}" : ""}") @@ -236,14 +293,17 @@ module Invidious::Routes::Channels return error_template(404, translate(locale, "This channel does not exist.")) end - selected_tab = env.request.path.split("/")[-1] - if {"home", "videos", "shorts", "streams", "playlists", "community", "channels", "about"}.includes? selected_tab + selected_tab = env.params.url["tab"]? + + if KNOWN_TABS.includes? selected_tab url = "/channel/#{ucid}/#{selected_tab}" else url = "/channel/#{ucid}" end - env.redirect url + url += "?#{invidious_url_params}" if !invidious_url_params.empty? + + return env.redirect url end # Handles redirects for the /profile endpoint diff --git a/src/invidious/routing.cr b/src/invidious/routing.cr index a5ee47b9..b4f079a5 100644 --- a/src/invidious/routing.cr +++ b/src/invidious/routing.cr @@ -136,28 +136,42 @@ module Invidious::Routing get "/channel/:ucid/community", Routes::Channels, :community get "/channel/:ucid/channels", Routes::Channels, :channels get "/channel/:ucid/about", Routes::Channels, :about + get "/channel/:ucid/live", Routes::Channels, :live get "/user/:user/live", Routes::Channels, :live get "/c/:user/live", Routes::Channels, :live + get "/post/:id", Routes::Channels, :post - {"", "/videos", "/shorts", "/streams", "/playlists", "/community", "/about"}.each do |path| - # /c/LinusTechTips - get "/c/:user#{path}", Routes::Channels, :brand_redirect - # /user/linustechtips | Not always the same as /c/ - get "/user/:user#{path}", Routes::Channels, :brand_redirect - # /@LinusTechTips | Handle - get "/@:user#{path}", Routes::Channels, :brand_redirect - # /attribution_link?a=anything&u=/channel/UCZYTClx2T1of7BRZ86-8fow - get "/attribution_link#{path}", Routes::Channels, :brand_redirect - # /profile?user=linustechtips - get "/profile/#{path}", Routes::Channels, :profile - end + # Channel catch-all, to redirect future routes to the channel's home + # NOTE: defined last in order to be processed after the other routes + get "/channel/:ucid/*", Routes::Channels, :redirect_home + + # /c/LinusTechTips + get "/c/:user", Routes::Channels, :brand_redirect + get "/c/:user/:tab", Routes::Channels, :brand_redirect + + # /user/linustechtips (Not always the same as /c/) + get "/user/:user", Routes::Channels, :brand_redirect + get "/user/:user/:tab", Routes::Channels, :brand_redirect + + # /@LinusTechTips (Handle) + get "/@:user", Routes::Channels, :brand_redirect + get "/@:user/:tab", Routes::Channels, :brand_redirect + + # /attribution_link?a=anything&u=/channel/UCZYTClx2T1of7BRZ86-8fow + get "/attribution_link", Routes::Channels, :brand_redirect + get "/attribution_link/:tab", Routes::Channels, :brand_redirect + + # /profile?user=linustechtips + get "/profile", Routes::Channels, :profile + get "/profile/*", Routes::Channels, :profile end def register_watch_routes get "/watch", Routes::Watch, :handle post "/watch_ajax", Routes::Watch, :mark_watched get "/watch/:id", Routes::Watch, :redirect + get "/live/:id", Routes::Watch, :redirect get "/shorts/:id", Routes::Watch, :redirect get "/clip/:clip", Routes::Watch, :clip get "/w/:id", Routes::Watch, :redirect @@ -256,6 +270,10 @@ module Invidious::Routing get "/api/v1/channels/:ucid/#{{{route}}}", {{namespace}}::Channels, :{{route}} {% end %} + # Posts + get "/api/v1/post/:id", {{namespace}}::Channels, :post + get "/api/v1/post/:id/comments", {{namespace}}::Channels, :post_comments + # 301 redirects to new /api/v1/channels/community/:ucid and /:ucid/community get "/api/v1/channels/comments/:ucid", {{namespace}}::Channels, :channel_comments_redirect get "/api/v1/channels/:ucid/comments", {{namespace}}::Channels, :channel_comments_redirect @@ -265,6 +283,7 @@ module Invidious::Routing get "/api/v1/search/suggestions", {{namespace}}::Search, :search_suggestions get "/api/v1/hashtag/:hashtag", {{namespace}}::Search, :hashtag + # Authenticated # The notification APIs cannot be extracted yet! They require the *local* notifications constant defined in invidious.cr diff --git a/src/invidious/videos/caption.cr b/src/invidious/videos/caption.cr index 256dfcc0..484e61d2 100644 --- a/src/invidious/videos/caption.cr +++ b/src/invidious/videos/caption.cr @@ -52,17 +52,13 @@ module Invidious::Videos break end end - result = String.build do |result| - result << <<-END_VTT - WEBVTT - Kind: captions - Language: #{tlang || @language_code} + settings_field = { + "Kind" => "captions", + "Language" => "#{tlang || @language_code}", + } - END_VTT - - result << "\n\n" - + result = WebVTT.build(settings_field) do |vtt| cues.each_with_index do |node, i| start_time = node["t"].to_f.milliseconds @@ -76,29 +72,16 @@ module Invidious::Videos end_time = start_time + duration end - # start_time - result << start_time.hours.to_s.rjust(2, '0') - result << ':' << start_time.minutes.to_s.rjust(2, '0') - result << ':' << start_time.seconds.to_s.rjust(2, '0') - result << '.' << start_time.milliseconds.to_s.rjust(3, '0') - - result << " --> " - - # end_time - result << end_time.hours.to_s.rjust(2, '0') - result << ':' << end_time.minutes.to_s.rjust(2, '0') - result << ':' << end_time.seconds.to_s.rjust(2, '0') - result << '.' << end_time.milliseconds.to_s.rjust(3, '0') - - result << "\n" - - node.children.each do |s| - result << s.content + text = String.build do |io| + node.children.each do |s| + io << s.content + end end - result << "\n" - result << "\n" + + vtt.cue(start_time, end_time, text) end end + return result end end diff --git a/src/invidious/videos/transcript.cr b/src/invidious/videos/transcript.cr index f3360a52..dac00eea 100644 --- a/src/invidious/videos/transcript.cr +++ b/src/invidious/videos/transcript.cr @@ -34,41 +34,15 @@ module Invidious::Videos # Convert into array of TranscriptLine lines = self.parse(initial_data) + settings_field = { + "Kind" => "captions", + "Language" => target_language, + } + # Taken from Invidious::Videos::Captions::Metadata.timedtext_to_vtt() - vtt = String.build do |vtt| - vtt << <<-END_VTT - WEBVTT - Kind: captions - Language: #{target_language} - - - END_VTT - - vtt << "\n\n" - + vtt = WebVTT.build(settings_field) do |vtt| lines.each do |line| - start_time = line.start_ms - end_time = line.end_ms - - # start_time - vtt << start_time.hours.to_s.rjust(2, '0') - vtt << ':' << start_time.minutes.to_s.rjust(2, '0') - vtt << ':' << start_time.seconds.to_s.rjust(2, '0') - vtt << '.' << start_time.milliseconds.to_s.rjust(3, '0') - - vtt << " --> " - - # end_time - vtt << end_time.hours.to_s.rjust(2, '0') - vtt << ':' << end_time.minutes.to_s.rjust(2, '0') - vtt << ':' << end_time.seconds.to_s.rjust(2, '0') - vtt << '.' << end_time.milliseconds.to_s.rjust(3, '0') - - vtt << "\n" - vtt << line.line - - vtt << "\n" - vtt << "\n" + vtt.cue(line.start_ms, line.end_ms, line.line) end end diff --git a/src/invidious/views/community.ecr b/src/invidious/views/community.ecr index 24efc34e..d2a305d3 100644 --- a/src/invidious/views/community.ecr +++ b/src/invidious/views/community.ecr @@ -26,7 +26,7 @@

<%= error_message %>

<% else %> -
+
<%= IV::Frontend::Comments.template_youtube(items.not_nil!, locale, thin_mode) %>
<% end %> diff --git a/src/invidious/views/components/item.ecr b/src/invidious/views/components/item.ecr index 4e960d8d..2155d74c 100644 --- a/src/invidious/views/components/item.ecr +++ b/src/invidious/views/components/item.ecr @@ -26,8 +26,9 @@
+ <% if !item.channel_handle.nil? %>

<%= item.channel_handle %>

<% end %>

<%= translate_count(locale, "generic_subscribers_count", item.subscriber_count, NumberFormatting::Separator) %>

- <% if !item.auto_generated %>

<%= translate_count(locale, "generic_videos_count", item.video_count, NumberFormatting::Separator) %>

<% end %> + <% if !item.auto_generated && item.channel_handle.nil? %>

<%= translate_count(locale, "generic_videos_count", item.video_count, NumberFormatting::Separator) %>

<% end %>
<%= item.description_html %>
<% when SearchHashtag %> <% if !thin_mode %> diff --git a/src/invidious/views/post.ecr b/src/invidious/views/post.ecr new file mode 100644 index 00000000..fb03a44c --- /dev/null +++ b/src/invidious/views/post.ecr @@ -0,0 +1,48 @@ +<% content_for "header" do %> +Invidious +<% end %> + +
+
+ <%= IV::Frontend::Comments.template_youtube(post_response.not_nil!, locale, thin_mode) %> +
+ + <% if nojs %> +
+ <% end %> +
+ +
+ <% if nojs %> + <%= comment_html %> + <% else %> + + <% end %> +
+
+ + + + \ No newline at end of file diff --git a/src/invidious/views/watch.ecr b/src/invidious/views/watch.ecr index 4c844534..0ccb10c8 100644 --- a/src/invidious/views/watch.ecr +++ b/src/invidious/views/watch.ecr @@ -67,7 +67,8 @@ we're going to need to do it here in order to allow for translations. "premiere_timestamp" => video.premiere_timestamp.try &.to_unix, "vr" => video.is_vr, "projection_type" => video.projection_type, - "local_disabled" => CONFIG.disabled?("local") + "local_disabled" => CONFIG.disabled?("local"), + "support_reddit" => true }.to_pretty_json %> @@ -301,7 +302,7 @@ we're going to need to do it here in order to allow for translations.
<% end %> -
+
<% if nojs %> <%= comment_html %> <% else %> @@ -385,4 +386,5 @@ we're going to need to do it here in order to allow for translations.
<% end %>
+ diff --git a/src/invidious/yt_backend/extractors.cr b/src/invidious/yt_backend/extractors.cr index aaf7772e..56325cf7 100644 --- a/src/invidious/yt_backend/extractors.cr +++ b/src/invidious/yt_backend/extractors.cr @@ -175,17 +175,18 @@ private module Parsers # Always simpleText # TODO change default value to nil - subscriber_count = item_contents.dig?("subscriberCountText", "simpleText") + subscriber_count = item_contents.dig?("subscriberCountText", "simpleText").try &.as_s + channel_handle = subscriber_count if (subscriber_count.try &.starts_with? "@") # Since youtube added channel handles, `VideoCountText` holds the number of # subscribers and `subscriberCountText` holds the handle, except when the # channel doesn't have a handle (e.g: some topic music channels). # See https://github.com/iv-org/invidious/issues/3394#issuecomment-1321261688 - if !subscriber_count || !subscriber_count.as_s.includes? " subscriber" - subscriber_count = item_contents.dig?("videoCountText", "simpleText") + if !subscriber_count || !subscriber_count.includes? " subscriber" + subscriber_count = item_contents.dig?("videoCountText", "simpleText").try &.as_s end subscriber_count = subscriber_count - .try { |s| short_text_to_number(s.as_s.split(" ")[0]).to_i32 } || 0 + .try { |s| short_text_to_number(s.split(" ")[0]).to_i32 } || 0 # Auto-generated channels doesn't have videoCountText # Taken from: https://github.com/iv-org/invidious/pull/2228#discussion_r717620922 @@ -200,6 +201,7 @@ private module Parsers author_thumbnail: author_thumbnail, subscriber_count: subscriber_count, video_count: video_count, + channel_handle: channel_handle, description_html: description_html, auto_generated: auto_generated, author_verified: author_verified,