diff --git a/locales/ar.json b/locales/ar.json
index 3ce34c2d..7303915b 100644
--- a/locales/ar.json
+++ b/locales/ar.json
@@ -545,5 +545,7 @@
"Album: ": "الألبوم: ",
"Artist: ": "الفنان: ",
"Song: ": "أغنية: ",
- "Channel Sponsor": "راعي القناة"
+ "Channel Sponsor": "راعي القناة",
+ "Standard YouTube license": "ترخيص YouTube القياسي",
+ "Download is disabled": "تم تعطيل التحميلات"
}
diff --git a/locales/ca.json b/locales/ca.json
index 54a0b177..901249ac 100644
--- a/locales/ca.json
+++ b/locales/ca.json
@@ -66,7 +66,7 @@
"Malay": "Malai",
"Persian": "Persa",
"Slovak": "Eslovac",
- "Search": "Busca",
+ "Search": "Cerca",
"Show annotations": "Mostra anotacions",
"preferences_region_label": "País del contingut: ",
"preferences_sort_label": "Ordena vídeos per: ",
@@ -75,7 +75,7 @@
"Title": "Títol",
"Belarusian": "Bielorús",
"Enable web notifications": "Activa notificacions web",
- "search": "cerca",
+ "search": "Cerca",
"Catalan": "Català",
"Croatian": "Croat",
"preferences_category_admin": "Preferències d'administrador",
@@ -122,8 +122,8 @@
"search_filters_features_option_location": "Ubicació",
"search_filters_apply_button": "Aplica els filtres seleccionats",
"videoinfo_started_streaming_x_ago": "Ha començat el directe fa `x`",
- "next_steps_error_message_go_to_youtube": "Anar a YouTube",
- "footer_donate_page": "Donar",
+ "next_steps_error_message_go_to_youtube": "Vés a YouTube",
+ "footer_donate_page": "Feu un donatiu",
"footer_original_source_code": "Codi font original",
"videoinfo_watch_on_youTube": "Veure a YouTube",
"user_saved_playlists": "`x` llistes de reproducció guardades",
@@ -164,7 +164,7 @@
"crash_page_report_issue": "Si cap de les anteriors no ha ajudat, obre un nou issue a GitHub (preferiblement en anglès) i inclou el text següent al missatge (NO tradueixis aquest text):",
"generic_subscriptions_count": "{{count}} subscripció",
"generic_subscriptions_count_plural": "{{count}} subscripcions",
- "error_video_not_in_playlist": "El vídeo sol·licitat no existeix en aquesta llista de reproducció. Fes clic aquí per a la pàgina d'inici de la llista de reproducció.",
+ "error_video_not_in_playlist": "El vídeo sol·licitat no existeix en aquesta llista de reproducció. Feu clic aquí per a la pàgina d'inici de la llista de reproducció.",
"comments_points_count": "{{count}} punt",
"comments_points_count_plural": "{{count}} punts",
"%A %B %-d, %Y": "%A %B %-d, %Y",
@@ -175,7 +175,7 @@
"preferences_unseen_only_label": "Mostra només no vistos: ",
"preferences_listen_label": "Escolta per defecte: ",
"Import": "Importar",
- "Token": "Senyal",
+ "Token": "Testimoni",
"Wilson score: ": "Puntuació de Wilson: ",
"search_filters_date_label": "Data de càrrega",
"search_filters_features_option_three_sixty": "360°",
@@ -184,10 +184,10 @@
"preferences_comments_label": "Comentaris per defecte: ",
"`x` uploaded a video": "`x` ha penjat un vídeo",
"Released under the AGPLv3 on Github.": "Publicat sota l'AGPLv3 a GitHub.",
- "Token manager": "Gestor de tokens",
+ "Token manager": "Gestor de testimonis",
"Watch history": "Historial de reproduccions",
"Cannot change password for Google accounts": "No es pot canviar la contrasenya dels comptes de Google",
- "Authorize token?": "Autoritzar senyal?",
+ "Authorize token?": "Autoritzar testimoni?",
"Source available here.": "Font disponible aquí.",
"Export subscriptions as OPML (for NewPipe & FreeTube)": "Exporta subscripcions com a OPML (per a NewPipe i FreeTube)",
"Log in": "Inicia sessió",
@@ -197,7 +197,7 @@
"Public": "Públic",
"View all playlists": "Veure totes les llistes de reproducció",
"reddit": "Reddit",
- "Manage tokens": "Gestiona senyals",
+ "Manage tokens": "Gestiona testimonis",
"Not a playlist.": "No és una llista de reproducció.",
"preferences_local_label": "Vídeos de Proxy: ",
"View channel on YouTube": "Veure canal a Youtube",
@@ -272,7 +272,7 @@
"Khmer": "Khmer",
"This channel does not exist.": "Aquest canal no existeix.",
"Song: ": "Cançó: ",
- "Login failed. This may be because two-factor authentication is not turned on for your account.": "Error a l'iniciar sessió. Això pot ser perquè l'autenticació de dos factors no està activada per al vostre compte.",
+ "Login failed. This may be because two-factor authentication is not turned on for your account.": "S'ha produït un error en iniciar sessió. Això pot ser perquè l'autenticació de dos factors no està activada per al vostre compte.",
"channel:`x`": "canal: `x`",
"Deleted or invalid channel": "Canal suprimit o no vàlid",
"Could not get channel info.": "No s'ha pogut obtenir la informació del canal.",
@@ -298,10 +298,10 @@
"generic_views_count_plural": "{{count}} visualitzacions",
"generic_videos_count": "{{count}} vídeo",
"generic_videos_count_plural": "{{count}} vídeos",
- "Token is expired, please try again": "La senyal ha caducat, torna-ho a provar",
+ "Token is expired, please try again": "El testimoni ha caducat, torna-ho a provar",
"English": "Anglès",
"Kannada": "Kanarès",
- "Erroneous token": "Senyal errònia",
+ "Erroneous token": "Testimoni erroni",
"`x` ago": "fa `x`",
"Empty playlist": "Llista de reproducció buida",
"Playlist does not exist.": "La llista de reproducció no existeix.",
@@ -376,7 +376,7 @@
"Clear watch history": "Neteja l'historial de reproduccions",
"Mongolian": "Mongol",
"preferences_quality_dash_option_best": "Millor",
- "Authorize token for `x`?": "Autoritzar senyal per a `x`?",
+ "Authorize token for `x`?": "Autoritzar testimoni per a `x`?",
"Report statistics: ": "Estadístiques de l'informe: ",
"Switch Invidious Instance": "Canvia la instància d'Invidious",
"History": "Historial",
@@ -410,7 +410,7 @@
"Export": "Exportar",
"preferences_quality_dash_option_4320p": "4320p",
"JavaScript license information": "Informació de la llicència de JavaScript",
- "Hidden field \"token\" is a required field": "El camp ocult \"senyal\" és un camp obligatori",
+ "Hidden field \"token\" is a required field": "El camp ocult \"testimoni\" és un camp obligatori",
"Shona": "Xona",
"Family friendly? ": "Apte per a tots els públics? ",
"preferences_quality_dash_label": "Qualitat de vídeo DASH preferida: ",
@@ -443,7 +443,7 @@
"unsubscribe": "cancel·la la subscripció",
"View playlist on YouTube": "Veure llista de reproducció a YouTube",
"Import NewPipe subscriptions (.json)": "Importar subscripcions de NewPipe (.json)",
- "crash_page_you_found_a_bug": "Sembla que has trobat un error a Invidious!",
+ "crash_page_you_found_a_bug": "Heu trobat un error a Invidious!",
"Subscribe": "Subscriu-me",
"Quota exceeded, try again in a few hours": "S'ha superat la quota, torna-ho a provar d'aquí a unes hores",
"generic_count_days": "{{count}} dia",
@@ -468,8 +468,8 @@
"revoke": "revocar",
"English (United Kingdom)": "Anglès (Regne Unit)",
"preferences_quality_option_hd720": "HD720",
- "tokens_count": "{{count}} senyal",
- "tokens_count_plural": "{{count}} senyals",
+ "tokens_count": "{{count}} testimoni",
+ "tokens_count_plural": "{{count}} testimonis",
"subscriptions_unseen_notifs_count": "{{count}} notificació no vista",
"subscriptions_unseen_notifs_count_plural": "{{count}} notificacions no vistes",
"generic_subscribers_count": "{{count}} subscriptor",
@@ -481,5 +481,7 @@
"Top": "Millors",
"preferences_max_results_label": "Nombre de vídeos mostrats al feed: ",
"Engagement: ": "Atracció: ",
- "Redirect homepage to feed: ": "Redirigeix la pàgina d'inici al feed: "
+ "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"
}
diff --git a/locales/cs.json b/locales/cs.json
index 4611c4fd..0e8610bf 100644
--- a/locales/cs.json
+++ b/locales/cs.json
@@ -497,5 +497,7 @@
"Artist: ": "Umělec: ",
"Album: ": "Album: ",
"Channel Sponsor": "Sponzor kanálu",
- "Song: ": "Skladba: "
+ "Song: ": "Skladba: ",
+ "Standard YouTube license": "Standardní licence YouTube",
+ "Download is disabled": "Stahování je zakázáno"
}
diff --git a/locales/de.json b/locales/de.json
index c2941d6d..0df86663 100644
--- a/locales/de.json
+++ b/locales/de.json
@@ -479,5 +479,8 @@
"Artist: ": "Künstler: ",
"Album: ": "Album: ",
"channel_tab_playlists_label": "Wiedergabelisten",
- "channel_tab_channels_label": "Kanäle"
+ "channel_tab_channels_label": "Kanäle",
+ "Channel Sponsor": "Kanalsponsor",
+ "Standard YouTube license": "Standard YouTube-Lizenz",
+ "Song: ": "Musik: "
}
diff --git a/locales/eo.json b/locales/eo.json
index 9f37c7cb..464d16ca 100644
--- a/locales/eo.json
+++ b/locales/eo.json
@@ -479,5 +479,9 @@
"channel_tab_shorts_label": "Mallongaj",
"Music in this video": "Muziko en ĉi tiu video",
"Artist: ": "Artisto: ",
- "Album: ": "Albumo: "
+ "Album: ": "Albumo: ",
+ "Channel Sponsor": "Kanala sponsoro",
+ "Song: ": "Muzikaĵo: ",
+ "Standard YouTube license": "Implicita YouTube-licenco",
+ "Download is disabled": "Elŝuto estas malebligita"
}
diff --git a/locales/es.json b/locales/es.json
index bb082c06..09f510a7 100644
--- a/locales/es.json
+++ b/locales/es.json
@@ -482,5 +482,7 @@
"Artist: ": "Artista: ",
"Album: ": "Álbum: ",
"Song: ": "Canción: ",
- "Channel Sponsor": "Patrocinador del canal"
+ "Channel Sponsor": "Patrocinador del canal",
+ "Standard YouTube license": "Licencia de YouTube estándar",
+ "Download is disabled": "La descarga está deshabilitada"
}
diff --git a/locales/fa.json b/locales/fa.json
index 56685f64..29a0c527 100644
--- a/locales/fa.json
+++ b/locales/fa.json
@@ -450,5 +450,8 @@
"Music in this video": "آهنگ در این ویدیو",
"Artist: ": "هنرمند: ",
"Album: ": "آلبوم: ",
- "Song: ": "آهنگ: "
+ "Song: ": "آهنگ: ",
+ "Channel Sponsor": "اسپانسر کانال",
+ "Standard YouTube license": "پروانه استاندارد YouTube",
+ "search_message_use_another_instance": " شما همچنین میتوانید در نمونه دیگر هم جستجو کنید."
}
diff --git a/locales/fr.json b/locales/fr.json
index 9d3e117f..bb40916b 100644
--- a/locales/fr.json
+++ b/locales/fr.json
@@ -474,7 +474,14 @@
"search_filters_duration_option_none": "Toutes les durées",
"error_video_not_in_playlist": "La vidéo demandée n'existe pas dans cette liste de lecture. Cliquez ici pour retourner à la liste de lecture.",
"channel_tab_shorts_label": "Clips",
- "channel_tab_streams_label": "En direct",
+ "channel_tab_streams_label": "Vidéos en direct",
"channel_tab_playlists_label": "Listes de lecture",
- "channel_tab_channels_label": "Chaînes"
+ "channel_tab_channels_label": "Chaînes",
+ "Song: ": "Chanson : ",
+ "Artist: ": "Artiste : ",
+ "Album: ": "Album : ",
+ "Standard YouTube license": "Licence YouTube Standard",
+ "Music in this video": "Musique dans cette vidéo",
+ "Channel Sponsor": "Soutien de la chaîne",
+ "Download is disabled": "Le téléchargement est désactivé"
}
diff --git a/locales/hr.json b/locales/hr.json
index ade732ad..b87a7729 100644
--- a/locales/hr.json
+++ b/locales/hr.json
@@ -497,5 +497,7 @@
"Album: ": "Album: ",
"Artist: ": "Izvođač: ",
"Channel Sponsor": "Sponzor kanala",
- "Song: ": "Pjesma: "
+ "Song: ": "Pjesma: ",
+ "Standard YouTube license": "Standardna YouTube licenca",
+ "Download is disabled": "Preuzimanje je deaktivirano"
}
diff --git a/locales/id.json b/locales/id.json
index 51d6d55c..f0adfdb1 100644
--- a/locales/id.json
+++ b/locales/id.json
@@ -453,5 +453,6 @@
"crash_page_switch_instance": "mencoba untuk menggunakan peladen lainnya",
"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):"
+ "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: "
}
diff --git a/locales/it.json b/locales/it.json
index c60f760b..0797b387 100644
--- a/locales/it.json
+++ b/locales/it.json
@@ -479,5 +479,9 @@
"channel_tab_community_label": "Comunità",
"Music in this video": "Musica in questo video",
"Artist: ": "Artista: ",
- "Album: ": "Album: "
+ "Album: ": "Album: ",
+ "Download is disabled": "Il download è disabilitato",
+ "Song: ": "Canzone: ",
+ "Standard YouTube license": "Licenza standard di YouTube",
+ "Channel Sponsor": "Sponsor del canale"
}
diff --git a/locales/ja.json b/locales/ja.json
index 8a4537d4..d1813bcd 100644
--- a/locales/ja.json
+++ b/locales/ja.json
@@ -465,5 +465,6 @@
"Artist: ": "アーティスト: ",
"Album: ": "アルバム: ",
"Song: ": "曲: ",
- "Channel Sponsor": "チャンネルのスポンサー"
+ "Channel Sponsor": "チャンネルのスポンサー",
+ "Standard YouTube license": "標準 Youtube ライセンス"
}
diff --git a/locales/lt.json b/locales/lt.json
index 9bfcfdba..91c7febe 100644
--- a/locales/lt.json
+++ b/locales/lt.json
@@ -488,5 +488,6 @@
"preferences_save_player_pos_label": "Išsaugoti atkūrimo padėtį: ",
"videoinfo_youTube_embed_link": "Įterpti",
"videoinfo_invidious_embed_link": "Įterpti nuorodą",
- "crash_page_refresh": "pabandėte atnaujinti puslapį"
+ "crash_page_refresh": "pabandėte atnaujinti puslapį",
+ "Album: ": "Albumas "
}
diff --git a/locales/pl.json b/locales/pl.json
index 3c713e70..2b6768d9 100644
--- a/locales/pl.json
+++ b/locales/pl.json
@@ -498,5 +498,6 @@
"Artist: ": "Wykonawca: ",
"Album: ": "Album: ",
"Song: ": "Piosenka: ",
- "Channel Sponsor": "Sponsor kanału"
+ "Channel Sponsor": "Sponsor kanału",
+ "Standard YouTube license": "Standardowa licencja YouTube"
}
diff --git a/locales/pt.json b/locales/pt.json
index 310381ae..cbce0e5a 100644
--- a/locales/pt.json
+++ b/locales/pt.json
@@ -481,5 +481,7 @@
"Artist: ": "Artista: ",
"Album: ": "Álbum: ",
"Song: ": "Canção: ",
- "Channel Sponsor": "Patrocinador do canal"
+ "Channel Sponsor": "Patrocinador do canal",
+ "Standard YouTube license": "Licença padrão do YouTube",
+ "Download is disabled": "A descarga está desativada"
}
diff --git a/locales/ru.json b/locales/ru.json
index 7ca5cf1f..0031f79a 100644
--- a/locales/ru.json
+++ b/locales/ru.json
@@ -4,7 +4,7 @@
"Unsubscribe": "Отписаться",
"Subscribe": "Подписаться",
"View channel on YouTube": "Смотреть канал на YouTube",
- "View playlist on YouTube": "Посмотреть плейлист на YouTube",
+ "View playlist on YouTube": "Просмотреть подборку на ютубе",
"newest": "сначала новые",
"oldest": "сначала старые",
"popular": "популярные",
@@ -14,7 +14,7 @@
"Clear watch history?": "Очистить историю просмотров?",
"New password": "Новый пароль",
"New passwords must match": "Новые пароли не совпадают",
- "Cannot change password for Google accounts": "Изменить пароль аккаунта Google невозможно",
+ "Cannot change password for Google accounts": "Изменить пароль учётной записи Google невозможно",
"Authorize token?": "Авторизовать токен?",
"Authorize token for `x`?": "Авторизовать токен для `x`?",
"Yes": "Да",
@@ -30,7 +30,7 @@
"Export subscriptions as OPML": "Экспортировать подписки в формате OPML",
"Export subscriptions as OPML (for NewPipe & FreeTube)": "Экспортировать подписки в формате OPML (для NewPipe и FreeTube)",
"Export data as JSON": "Экспортировать данные Invidious в формате JSON",
- "Delete account?": "Удалить аккаунт?",
+ "Delete account?": "Удалить учётку?",
"History": "История",
"An alternative front-end to YouTube": "Альтернативный фронтенд для YouTube",
"JavaScript license information": "Информация о лицензиях JavaScript",
@@ -38,14 +38,14 @@
"Log in": "Войти",
"Log in/register": "Войти или зарегистрироваться",
"Log in with Google": "Войти через Google",
- "User ID": "ID пользователя",
+ "User ID": "ИД пользователя",
"Password": "Пароль",
"Time (h:mm:ss):": "Время (ч:мм:сс):",
"Text CAPTCHA": "Текстовая капча (англ.)",
"Image CAPTCHA": "Капча-картинка",
"Sign In": "Войти",
"Register": "Зарегистрироваться",
- "E-mail": "Электронная почта",
+ "E-mail": "Эл. почта",
"Google verification code": "Код подтверждения Google",
"Preferences": "Настройки",
"preferences_category_player": "Настройки проигрывателя",
@@ -69,11 +69,11 @@
"preferences_vr_mode_label": "Интерактивные 360-градусные видео (необходим WebGL): ",
"preferences_category_visual": "Настройки сайта",
"preferences_player_style_label": "Стиль проигрывателя: ",
- "Dark mode: ": "Темное оформление: ",
+ "Dark mode: ": "Тёмное оформление: ",
"preferences_dark_mode_label": "Тема: ",
- "dark": "темная",
+ "dark": "тёмная",
"light": "светлая",
- "preferences_thin_mode_label": "Облегченное оформление: ",
+ "preferences_thin_mode_label": "Облегчённое оформление: ",
"preferences_category_misc": "Прочие настройки",
"preferences_automatic_instance_redirect_label": "Автоматическая смена зеркала (переход на redirect.invidious.io): ",
"preferences_category_subscription": "Настройки подписок",
@@ -129,14 +129,14 @@
"Public": "Публичный",
"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`",
"Show more": "Развернуть",
"Show less": "Свернуть",
"Watch on YouTube": "Смотреть на YouTube",
@@ -147,13 +147,13 @@
"License: ": "Лицензия: ",
"Family friendly? ": "Семейный просмотр: ",
"Wilson score: ": "Оценка Уилсона: ",
- "Engagement: ": "Вовлеченность: ",
+ "Engagement: ": "Вовлечённость: ",
"Whitelisted regions: ": "Доступно в регионах: ",
"Blacklisted regions: ": "Недоступно в регионах: ",
"Shared `x`": "Опубликовано `x`",
"Premieres in `x`": "Премьера через `x`",
"Premieres `x`": "Премьера `x`",
- "Hi! Looks like you have JavaScript turned off. Click here to view comments, keep in mind they may take a bit longer to load.": "Похоже, у вас отключен JavaScript. Нажмите сюда, чтобы увидеть комментарии. Но учтите: они могут загружаться немного медленнее.",
+ "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 `x` comments": {
@@ -171,7 +171,7 @@
"Wrong answer": "Неправильный ответ",
"Erroneous CAPTCHA": "Неправильная капча",
"CAPTCHA is a required field": "Необходимо решить капчу",
- "User ID is a required field": "Необходимо ввести ID пользователя",
+ "User ID is a required field": "Необходимо ввести идентификатор пользователя",
"Password is a required field": "Необходимо ввести пароль",
"Wrong username or password": "Неправильный логин или пароль",
"Please sign in using 'Log in with Google'": "Пожалуйста, нажмите «Войти через Google»",
@@ -180,23 +180,23 @@
"Please log in": "Пожалуйста, войдите",
"Invidious Private Feed for `x`": "Приватная лента Invidious для `x`",
"channel:`x`": "канал: `x`",
- "Deleted or invalid channel": "Канал удален или не найден",
+ "Deleted or invalid channel": "Канал удалён или не найден",
"This channel does not exist.": "Такого канала не существует.",
- "Could not get channel info.": "Не удается получить информацию об этом канале.",
- "Could not fetch comments": "Не удается загрузить комментарии",
+ "Could not get channel info.": "Не удаётся получить информацию об этом канале.",
+ "Could not fetch comments": "Не удаётся загрузить комментарии",
"`x` ago": "`x` назад",
- "Load more": "Загрузить еще",
+ "Load more": "Загрузить ещё",
"Could not create mix.": "Не удалось создать микс.",
- "Empty playlist": "Плейлист пуст",
- "Not a playlist.": "Это не плейлист.",
- "Playlist does not exist.": "Плейлист не существует.",
- "Could not pull trending pages.": "Не удается загрузить страницы «в тренде».",
+ "Empty playlist": "Подборка пуста",
+ "Not a playlist.": "Это не подборка.",
+ "Playlist does not exist.": "Подборка не существует.",
+ "Could not pull trending pages.": "Не удаётся загрузить страницы «в тренде».",
"Hidden field \"challenge\" is a required field": "Необходимо заполнить скрытое поле «challenge»",
"Hidden field \"token\" is a required field": "Необходимо заполнить скрытое поле «токен»",
"Erroneous challenge": "Неправильный ответ в «challenge»",
"Erroneous token": "Неправильный токен",
"No such user": "Пользователь не найден",
- "Token is expired, please try again": "Срок действия токена истек, попробуйте позже",
+ "Token is expired, please try again": "Срок действия токена истёк, попробуйте позже",
"English": "Английский",
"English (auto-generated)": "Английский (созданы автоматически)",
"Afrikaans": "Африкаанс",
@@ -213,7 +213,7 @@
"Burmese": "Бирманский",
"Catalan": "Каталонский",
"Cebuano": "Себуанский",
- "Chinese (Simplified)": "Китайский (упрощенный)",
+ "Chinese (Simplified)": "Китайский (упрощённый)",
"Chinese (Traditional)": "Китайский (традиционный)",
"Corsican": "Корсиканский",
"Croatian": "Хорватский",
@@ -310,7 +310,7 @@
"About": "О сайте",
"Rating: ": "Рейтинг: ",
"preferences_locale_label": "Язык: ",
- "View as playlist": "Смотреть как плейлист",
+ "View as playlist": "Смотреть как подборку",
"Default": "По умолчанию",
"Music": "Музыка",
"Gaming": "Игры",
@@ -326,7 +326,7 @@
"Audio mode": "Аудио режим",
"Video mode": "Видео режим",
"channel_tab_videos_label": "Видео",
- "Playlists": "Плейлисты",
+ "Playlists": "Подборки",
"channel_tab_community_label": "Сообщество",
"search_filters_sort_option_relevance": "по актуальности",
"search_filters_sort_option_rating": "по рейтингу",
@@ -343,7 +343,7 @@
"search_filters_date_option_year": "Этот год",
"search_filters_type_option_video": "Видео",
"search_filters_type_option_channel": "Канал",
- "search_filters_type_option_playlist": "Плейлист",
+ "search_filters_type_option_playlist": "Подборка",
"search_filters_type_option_movie": "Фильм",
"search_filters_type_option_show": "Сериал",
"search_filters_features_option_hd": "HD",
@@ -379,13 +379,13 @@
"Turkish (auto-generated)": "Турецкий (созданы автоматически)",
"Vietnamese (auto-generated)": "Вьетнамский (созданы автоматически)",
"footer_documentation": "Документация",
- "adminprefs_modified_source_code_url_label": "URL-адрес репозитория измененного исходного кода",
+ "adminprefs_modified_source_code_url_label": "Ссылка на репозиторий с измененными исходными кодами",
"none": "ничего",
"videoinfo_watch_on_youTube": "Смотреть на YouTube",
"videoinfo_youTube_embed_link": "Версия для встраивания",
"videoinfo_invidious_embed_link": "Ссылка для встраивания",
"download_subtitles": "Субтитры - `x` (.vtt)",
- "user_created_playlists": "`x` созданных плейлистов",
+ "user_created_playlists": "`x` созданных подборок",
"crash_page_you_found_a_bug": "Похоже, вы нашли ошибку в Invidious!",
"crash_page_before_reporting": "Прежде чем сообщать об ошибке, убедитесь, что вы:",
"crash_page_refresh": "пробовали перезагрузить страницу",
@@ -393,9 +393,9 @@
"generic_videos_count_0": "{{count}} видео",
"generic_videos_count_1": "{{count}} видео",
"generic_videos_count_2": "{{count}} видео",
- "generic_playlists_count_0": "{{count}} плейлист",
- "generic_playlists_count_1": "{{count}} плейлиста",
- "generic_playlists_count_2": "{{count}} плейлистов",
+ "generic_playlists_count_0": "{{count}} подборка",
+ "generic_playlists_count_1": "{{count}} подборки",
+ "generic_playlists_count_2": "{{count}} подборок",
"tokens_count_0": "{{count}} токен",
"tokens_count_1": "{{count}} токена",
"tokens_count_2": "{{count}} токенов",
@@ -453,8 +453,8 @@
"Portuguese (Brazil)": "Португальский (Бразилия)",
"footer_source_code": "Исходный код",
"footer_original_source_code": "Оригинальный исходный код",
- "footer_modfied_source_code": "Измененный исходный код",
- "user_saved_playlists": "`x` сохраненных плейлистов",
+ "footer_modfied_source_code": "Изменённый исходный код",
+ "user_saved_playlists": "`x` сохранённых подборок",
"crash_page_search_issue": "поискали похожую проблему на GitHub",
"comments_points_count_0": "{{count}} плюс",
"comments_points_count_1": "{{count}} плюса",
@@ -488,12 +488,16 @@
"search_filters_duration_option_medium": "Средние (4 - 20 минут)",
"search_filters_apply_button": "Применить фильтры",
"Popular enabled: ": "Популярное включено: ",
- "error_video_not_in_playlist": "Запрошенного видео нет в этом плейлисте. Нажмите тут, чтобы вернуться к странице плейлиста.",
- "channel_tab_playlists_label": "Плейлисты",
+ "error_video_not_in_playlist": "Запрошенного видео нет в этой подборке. Нажмите тут, чтобы вернуться к странице подборки.",
+ "channel_tab_playlists_label": "Подборки",
"channel_tab_channels_label": "Каналы",
"channel_tab_streams_label": "Живое вещание",
"channel_tab_shorts_label": "Shorts",
"Music in this video": "Музыка в этом видео",
"Artist: ": "Исполнитель: ",
- "Album: ": "Альбом: "
+ "Album: ": "Альбом: ",
+ "Song: ": "Композиция: ",
+ "Standard YouTube license": "Стандартная лицензия YouTube",
+ "Channel Sponsor": "Спонсор канала",
+ "Download is disabled": "Загрузка отключена"
}
diff --git a/locales/sl.json b/locales/sl.json
index 47f295e0..410b432c 100644
--- a/locales/sl.json
+++ b/locales/sl.json
@@ -511,5 +511,9 @@
"channel_tab_streams_label": "Prenosi v živo",
"Artist: ": "Umetnik/ca: ",
"Music in this video": "Glasba v tem videoposnetku",
- "Album: ": "Album: "
+ "Album: ": "Album: ",
+ "Song: ": "Pesem: ",
+ "Standard YouTube license": "Standardna licenca YouTube",
+ "Channel Sponsor": "Sponzor kanala",
+ "Download is disabled": "Prenos je onemogočen"
}
diff --git a/locales/tr.json b/locales/tr.json
index 6e0bc175..a2fdd573 100644
--- a/locales/tr.json
+++ b/locales/tr.json
@@ -481,5 +481,7 @@
"Music in this video": "Bu videodaki müzik",
"Artist: ": "Sanatçı: ",
"Channel Sponsor": "Kanal Sponsoru",
- "Song: ": "Şarkı: "
+ "Song: ": "Şarkı: ",
+ "Standard YouTube license": "Standart YouTube lisansı",
+ "Download is disabled": "İndirme devre dışı"
}
diff --git a/locales/uk.json b/locales/uk.json
index 4d748e7f..61bf3d31 100644
--- a/locales/uk.json
+++ b/locales/uk.json
@@ -497,5 +497,7 @@
"Artist: ": "Виконавець: ",
"Album: ": "Альбом: ",
"Song: ": "Пісня: ",
- "Channel Sponsor": "Спонсор каналу"
+ "Channel Sponsor": "Спонсор каналу",
+ "Standard YouTube license": "Стандартна ліцензія YouTube",
+ "Download is disabled": "Завантаження вимкнено"
}
diff --git a/locales/zh-CN.json b/locales/zh-CN.json
index f202cf88..df31812a 100644
--- a/locales/zh-CN.json
+++ b/locales/zh-CN.json
@@ -465,5 +465,7 @@
"channel_tab_shorts_label": "短视频",
"channel_tab_channels_label": "频道",
"Song: ": "歌曲: ",
- "Channel Sponsor": "频道赞助者"
+ "Channel Sponsor": "频道赞助者",
+ "Standard YouTube license": "标准 YouTube 许可证",
+ "Download is disabled": "已禁用下载"
}
diff --git a/locales/zh-TW.json b/locales/zh-TW.json
index 54090d3d..daa22493 100644
--- a/locales/zh-TW.json
+++ b/locales/zh-TW.json
@@ -465,5 +465,7 @@
"Album: ": "專輯: ",
"Music in this video": "此影片中的音樂",
"Channel Sponsor": "頻道贊助者",
- "Song: ": "歌曲: "
+ "Song: ": "歌曲: ",
+ "Standard YouTube license": "標準 YouTube 授權條款",
+ "Download is disabled": "已停用下載"
}
diff --git a/src/invidious/channels/community.cr b/src/invidious/channels/community.cr
index ce34ff82..ad786f3a 100644
--- a/src/invidious/channels/community.cr
+++ b/src/invidious/channels/community.cr
@@ -31,18 +31,16 @@ def fetch_channel_community(ucid, continuation, locale, format, thin_mode)
session_token: session_token,
}
- response = YT_POOL.client &.post("/comment_service_ajax?action_get_comments=1&ctoken=#{continuation}&continuation=#{continuation}&hl=en&gl=US", headers, form: post_req)
- body = JSON.parse(response.body)
+ body = YoutubeAPI.browse(continuation)
- body = body["response"]["continuationContents"]["itemSectionContinuation"]? ||
- body["response"]["continuationContents"]["backstageCommentsContinuation"]?
+ body = body.dig?("continuationContents", "itemSectionContinuation") ||
+ body.dig?("continuationContents", "backstageCommentsContinuation")
if !body
raise InfoException.new("Could not extract continuation.")
end
end
- continuation = body["continuations"]?.try &.[0]["nextContinuationData"]["continuation"].as_s
posts = body["contents"].as_a
if message = posts[0]["messageRenderer"]?
@@ -270,10 +268,8 @@ def fetch_channel_community(ucid, continuation, locale, format, thin_mode)
end
end
end
-
- if body["continuations"]?
- continuation = body["continuations"][0]["nextContinuationData"]["continuation"].as_s
- json.field "continuation", extract_channel_community_cursor(continuation)
+ if cont = posts.dig?(-1, "continuationItemRenderer", "continuationEndpoint", "continuationCommand", "token")
+ json.field "continuation", extract_channel_community_cursor(cont.as_s)
end
end
end
diff --git a/src/invidious/comments.cr b/src/invidious/comments.cr
index fd2be73d..ec4449f0 100644
--- a/src/invidious/comments.cr
+++ b/src/invidious/comments.cr
@@ -604,7 +604,7 @@ def text_to_parsed_content(text : String) : JSON::Any
currentNode = {"text" => urlMatch[0], "navigationEndpoint" => {"urlEndpoint" => {"url" => urlMatch[0]}}}
currentNodes << (JSON.parse(currentNode.to_json))
# If text remain after match create new simple node with text after match
- afterNode = {"text" => splittedLastNode.size > 0 ? splittedLastNode[1] : ""}
+ afterNode = {"text" => splittedLastNode.size > 1 ? splittedLastNode[1] : ""}
currentNodes << (JSON.parse(afterNode.to_json))
end
@@ -635,55 +635,8 @@ def content_to_comment_html(content, video_id : String? = "")
text = HTML.escape(run["text"].as_s)
- if run["navigationEndpoint"]?
- if url = run["navigationEndpoint"]["urlEndpoint"]?.try &.["url"].as_s
- url = URI.parse(url)
- displayed_url = text
-
- if url.host == "youtu.be"
- url = "/watch?v=#{url.request_target.lstrip('/')}"
- elsif url.host.nil? || url.host.not_nil!.ends_with?("youtube.com")
- if url.path == "/redirect"
- # Sometimes, links can be corrupted (why?) so make sure to fallback
- # nicely. See https://github.com/iv-org/invidious/issues/2682
- url = url.query_params["q"]? || ""
- displayed_url = url
- else
- url = url.request_target
- displayed_url = "youtube.com#{url}"
- end
- end
-
- text = %(#{reduce_uri(displayed_url)})
- elsif watch_endpoint = run["navigationEndpoint"]["watchEndpoint"]?
- start_time = watch_endpoint["startTimeSeconds"]?.try &.as_i
- link_video_id = watch_endpoint["videoId"].as_s
-
- url = "/watch?v=#{link_video_id}"
- url += "&t=#{start_time}" if !start_time.nil?
-
- # If the current video ID (passed through from the caller function)
- # is the same as the video ID in the link, add HTML attributes for
- # the JS handler function that bypasses page reload.
- #
- # See: https://github.com/iv-org/invidious/issues/3063
- if link_video_id == video_id
- start_time ||= 0
- text = %(#{reduce_uri(text)})
- else
- text = %(#{text})
- end
- elsif url = run.dig?("navigationEndpoint", "commandMetadata", "webCommandMetadata", "url").try &.as_s
- if text.starts_with?(/\s?[@#]/)
- # Handle "pings" in comments and hasthags differently
- # See:
- # - https://github.com/iv-org/invidious/issues/3038
- # - https://github.com/iv-org/invidious/issues/3062
- text = %(#{text})
- else
- text = %(#{reduce_uri(url)})
- end
- end
+ if navigationEndpoint = run.dig?("navigationEndpoint")
+ text = parse_link_endpoint(navigationEndpoint, text, video_id)
end
text = "#{text}" if run["bold"]?
diff --git a/src/invidious/database/users.cr b/src/invidious/database/users.cr
index 0a4a4fd8..d54e6a76 100644
--- a/src/invidious/database/users.cr
+++ b/src/invidious/database/users.cr
@@ -52,7 +52,7 @@ module Invidious::Database::Users
def mark_watched(user : User, vid : String)
request = <<-SQL
UPDATE users
- SET watched = array_append(watched, $1)
+ SET watched = array_append(array_remove(watched, $1), $1)
WHERE email = $2
SQL
diff --git a/src/invidious/helpers/utils.cr b/src/invidious/helpers/utils.cr
index 500a2582..bcf7c963 100644
--- a/src/invidious/helpers/utils.cr
+++ b/src/invidious/helpers/utils.cr
@@ -389,3 +389,56 @@ def reduce_uri(uri : URI | String, max_length : Int32 = 50, suffix : String = "
end
return str
end
+
+# Get the html link from a NavigationEndpoint or an innertubeCommand
+def parse_link_endpoint(endpoint : JSON::Any, text : String, video_id : String)
+ if url = endpoint.dig?("urlEndpoint", "url").try &.as_s
+ url = URI.parse(url)
+ displayed_url = text
+
+ if url.host == "youtu.be"
+ url = "/watch?v=#{url.request_target.lstrip('/')}"
+ elsif url.host.nil? || url.host.not_nil!.ends_with?("youtube.com")
+ if url.path == "/redirect"
+ # Sometimes, links can be corrupted (why?) so make sure to fallback
+ # nicely. See https://github.com/iv-org/invidious/issues/2682
+ url = url.query_params["q"]? || ""
+ displayed_url = url
+ else
+ url = url.request_target
+ displayed_url = "youtube.com#{url}"
+ end
+ end
+
+ text = %(#{reduce_uri(displayed_url)})
+ elsif watch_endpoint = endpoint.dig?("watchEndpoint")
+ start_time = watch_endpoint["startTimeSeconds"]?.try &.as_i
+ link_video_id = watch_endpoint["videoId"].as_s
+
+ url = "/watch?v=#{link_video_id}"
+ url += "&t=#{start_time}" if !start_time.nil?
+
+ # If the current video ID (passed through from the caller function)
+ # is the same as the video ID in the link, add HTML attributes for
+ # the JS handler function that bypasses page reload.
+ #
+ # See: https://github.com/iv-org/invidious/issues/3063
+ if link_video_id == video_id
+ start_time ||= 0
+ text = %(#{reduce_uri(text)})
+ else
+ text = %(#{text})
+ end
+ elsif url = endpoint.dig?("commandMetadata", "webCommandMetadata", "url").try &.as_s
+ if text.starts_with?(/\s?[@#]/)
+ # Handle "pings" in comments and hasthags differently
+ # See:
+ # - https://github.com/iv-org/invidious/issues/3038
+ # - https://github.com/iv-org/invidious/issues/3062
+ text = %(#{text})
+ else
+ text = %(#{reduce_uri(url)})
+ end
+ end
+ return text
+end
diff --git a/src/invidious/routes/watch.cr b/src/invidious/routes/watch.cr
index 5d3845c3..813cb0f4 100644
--- a/src/invidious/routes/watch.cr
+++ b/src/invidious/routes/watch.cr
@@ -76,7 +76,7 @@ module Invidious::Routes::Watch
end
env.params.query.delete_all("iv_load_policy")
- if watched && preferences.watch_history && !watched.includes? id
+ if watched && preferences.watch_history
Invidious::Database::Users.mark_watched(user.as(User), id)
end
@@ -259,9 +259,7 @@ module Invidious::Routes::Watch
case action
when "action_mark_watched"
- if !user.watched.includes? id
- Invidious::Database::Users.mark_watched(user, id)
- end
+ Invidious::Database::Users.mark_watched(user, id)
when "action_mark_unwatched"
Invidious::Database::Users.mark_unwatched(user, id)
else
diff --git a/src/invidious/search/processors.cr b/src/invidious/search/processors.cr
index 7e909590..25edb936 100644
--- a/src/invidious/search/processors.cr
+++ b/src/invidious/search/processors.cr
@@ -10,7 +10,7 @@ module Invidious::Search
initial_data = YoutubeAPI.search(query.text, search_params, client_config: client_config)
items, _ = extract_items(initial_data)
- return items
+ return items.reject!(Category)
end
# Search a youtube channel
@@ -32,7 +32,7 @@ module Invidious::Search
response_json = YoutubeAPI.browse(continuation)
items, _ = extract_items(response_json, "", ucid)
- return items
+ return items.reject!(Category)
end
# Search inside of user subscriptions
diff --git a/src/invidious/search/query.cr b/src/invidious/search/query.cr
index 24e79609..e38845d9 100644
--- a/src/invidious/search/query.cr
+++ b/src/invidious/search/query.cr
@@ -113,7 +113,7 @@ module Invidious::Search
case @type
when .regular?, .playlist?
- items = unnest_items(Processors.regular(self))
+ items = Processors.regular(self)
#
when .channel?
items = Processors.channel(self)
@@ -136,26 +136,5 @@ module Invidious::Search
return params
end
-
- # TODO: clean code
- private def unnest_items(all_items) : Array(SearchItem)
- items = [] of SearchItem
-
- # Light processing to flatten search results out of Categories.
- # They should ideally be supported in the future.
- all_items.each do |i|
- if i.is_a? Category
- i.contents.each do |nest_i|
- if !nest_i.is_a? Video
- items << nest_i
- end
- end
- else
- items << i
- end
- end
-
- return items
- end
end
end
diff --git a/src/invidious/trending.cr b/src/invidious/trending.cr
index 134eb437..2d9f8a83 100644
--- a/src/invidious/trending.cr
+++ b/src/invidious/trending.cr
@@ -17,7 +17,24 @@ def fetch_trending(trending_type, region, locale)
client_config = YoutubeAPI::ClientConfig.new(region: region)
initial_data = YoutubeAPI.browse("FEtrending", params: params, client_config: client_config)
- trending = extract_videos(initial_data)
- return {trending, plid}
+ items, _ = extract_items(initial_data)
+
+ extracted = [] of SearchItem
+
+ items.each do |itm|
+ if itm.is_a?(Category)
+ # Ignore the smaller categories, as they generally contain a sponsored
+ # channel, which brings a lot of noise on the trending page.
+ # See: https://github.com/iv-org/invidious/issues/2989
+ next if itm.contents.size < 24
+
+ extracted.concat extract_category(itm)
+ else
+ extracted << itm
+ end
+ end
+
+ # Deduplicate items before returning results
+ return extracted.select(SearchVideo).uniq!(&.id), plid
end
diff --git a/src/invidious/user/imports.cr b/src/invidious/user/imports.cr
index 20ae0d47..aa947456 100644
--- a/src/invidious/user/imports.cr
+++ b/src/invidious/user/imports.cr
@@ -48,7 +48,7 @@ struct Invidious::User
if data["watch_history"]?
user.watched += data["watch_history"].as_a.map(&.as_s)
- user.watched.uniq!
+ user.watched.reverse!.uniq!.reverse!
Invidious::Database::Users.update_watch_history(user)
end
diff --git a/src/invidious/videos/description.cr b/src/invidious/videos/description.cr
index 2017955d..542cb416 100644
--- a/src/invidious/videos/description.cr
+++ b/src/invidious/videos/description.cr
@@ -1,51 +1,6 @@
require "json"
require "uri"
-def parse_command(command : JSON::Any?, string : String) : String?
- on_tap = command.dig?("onTap", "innertubeCommand")
-
- # 3rd party URL, extract original URL from YouTube tracking URL
- if url_endpoint = on_tap.try &.["urlEndpoint"]?
- youtube_url = URI.parse url_endpoint["url"].as_s
-
- original_url = youtube_url.query_params["q"]?
- if original_url.nil?
- return ""
- else
- return "#{original_url}"
- end
- # 1st party watch URL
- elsif watch_endpoint = on_tap.try &.["watchEndpoint"]?
- video_id = watch_endpoint["videoId"].as_s
- time = watch_endpoint["startTimeSeconds"].as_i
-
- url = "/watch?v=#{video_id}&t=#{time}s"
-
- # if string is a timestamp, use the string instead
- # this is a lazy regex for validating timestamps
- if /(?:\d{1,2}:){1,2}\d{2}/ =~ string
- return "#{string}"
- else
- return "#{url}"
- end
- # hashtag/other browse URLs
- elsif browse_endpoint = on_tap.try &.dig?("commandMetadata", "webCommandMetadata")
- url = browse_endpoint["url"].try &.as_s
-
- # remove unnecessary character in a channel name
- if browse_endpoint["webPageType"]?.try &.as_s == "WEB_PAGE_TYPE_CHANNEL"
- name = string.match(/@[\w\d.-]+/)
- if name.try &.[0]?
- return "#{name.try &.[0]}"
- end
- end
-
- return "#{string}"
- end
-
- return "(unknown YouTube desc command)"
-end
-
private def copy_string(str : String::Builder, iter : Iterator, count : Int) : Int
copied = 0
while copied < count
@@ -62,7 +17,7 @@ private def copy_string(str : String::Builder, iter : Iterator, count : Int) : I
return copied
end
-def parse_description(desc : JSON::Any?) : String?
+def parse_description(desc, video_id : String) : String?
return "" if desc.nil?
content = desc["content"].as_s
@@ -94,7 +49,11 @@ def parse_description(desc : JSON::Any?) : String?
copy_string(str2, iter, cmd_length)
end
- str << parse_command(command, cmd_content)
+ link = cmd_content
+ if on_tap = command.dig?("onTap", "innertubeCommand")
+ link = parse_link_endpoint(on_tap, cmd_content, video_id)
+ end
+ str << link
index += cmd_length
end
diff --git a/src/invidious/videos/parser.cr b/src/invidious/videos/parser.cr
index 1c6d118d..2e8eecc3 100644
--- a/src/invidious/videos/parser.cr
+++ b/src/invidious/videos/parser.cr
@@ -287,7 +287,7 @@ def parse_video_info(video_id : String, player_response : Hash(String, JSON::Any
# description_html = video_secondary_renderer.try &.dig?("description", "runs")
# .try &.as_a.try { |t| content_to_comment_html(t, video_id) }
- description_html = parse_description(video_secondary_renderer.try &.dig?("attributedDescription"))
+ description_html = parse_description(video_secondary_renderer.try &.dig?("attributedDescription"), video_id)
# Video metadata
diff --git a/src/invidious/yt_backend/extractors_utils.cr b/src/invidious/yt_backend/extractors_utils.cr
index 0cb3c079..11d95958 100644
--- a/src/invidious/yt_backend/extractors_utils.cr
+++ b/src/invidious/yt_backend/extractors_utils.cr
@@ -68,19 +68,17 @@ rescue ex
return false
end
-def extract_videos(initial_data : Hash(String, JSON::Any), author_fallback : String? = nil, author_id_fallback : String? = nil) : Array(SearchVideo)
- extracted, _ = extract_items(initial_data, author_fallback, author_id_fallback)
+# This function extracts SearchVideo items from a Category.
+# Categories are commonly returned in search results and trending pages.
+def extract_category(category : Category) : Array(SearchVideo)
+ return category.contents.select(SearchVideo)
+end
- target = [] of (SearchItem | Continuation)
- extracted.each do |i|
- if i.is_a?(Category)
- i.contents.each { |cate_i| target << cate_i if !cate_i.is_a? Video }
- else
- target << i
- end
+# :ditto:
+def extract_category(category : Category, &)
+ category.contents.select(SearchVideo).each do |item|
+ yield item
end
-
- return target.select(SearchVideo)
end
def extract_selected_tab(tabs)