summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--assets/css/default.css1
-rw-r--r--assets/js/notifications.js4
-rw-r--r--docker-compose.yml3
-rw-r--r--locales/ar.json6
-rw-r--r--locales/bg.json3
-rw-r--r--locales/ca.json3
-rw-r--r--locales/cs.json4
-rw-r--r--locales/da.json37
-rw-r--r--locales/de.json5
-rw-r--r--locales/en-US.json3
-rw-r--r--locales/es.json109
-rw-r--r--locales/fa.json77
-rw-r--r--locales/fi.json2
-rw-r--r--locales/fr.json3
-rw-r--r--locales/hi.json7
-rw-r--r--locales/hr.json3
-rw-r--r--locales/ia.json41
-rw-r--r--locales/id.json3
-rw-r--r--locales/it.json3
-rw-r--r--locales/ja.json21
-rw-r--r--locales/ko.json7
-rw-r--r--locales/nb-NO.json3
-rw-r--r--locales/nl.json33
-rw-r--r--locales/pl.json6
-rw-r--r--locales/pt-BR.json4
-rw-r--r--locales/pt.json99
-rw-r--r--locales/ru.json37
-rw-r--r--locales/sl.json3
-rw-r--r--locales/sq.json11
-rw-r--r--locales/sr.json3
-rw-r--r--locales/sr_Cyrl.json4
-rw-r--r--locales/sv-SE.json192
-rw-r--r--locales/tr.json4
-rw-r--r--locales/uk.json4
-rw-r--r--locales/vi.json309
-rw-r--r--locales/zh-CN.json3
-rw-r--r--locales/zh-TW.json3
-rw-r--r--spec/i18next_plurals_spec.cr13
-rw-r--r--src/invidious.cr1
-rw-r--r--src/invidious/database/statistics.cr4
-rw-r--r--src/invidious/helpers/handlers.cr68
-rw-r--r--src/invidious/helpers/helpers.cr27
-rw-r--r--src/invidious/helpers/i18next.cr22
-rw-r--r--src/invidious/helpers/json_filter.cr248
-rw-r--r--src/invidious/helpers/utils.cr2
-rw-r--r--src/invidious/jobs/statistics_refresh_job.cr4
-rw-r--r--src/invidious/routes/api/v1/misc.cr2
-rw-r--r--src/invidious/routes/api/v1/search.cr7
-rw-r--r--src/invidious/routes/api/v1/videos.cr43
-rw-r--r--src/invidious/routes/feeds.cr25
-rw-r--r--src/invidious/routes/watch.cr6
-rw-r--r--src/invidious/routing.cr1
-rw-r--r--src/invidious/videos/clip.cr22
-rw-r--r--src/invidious/views/components/item.ecr36
-rw-r--r--src/invidious/views/template.ecr21
-rw-r--r--src/invidious/views/watch.ecr4
-rw-r--r--src/invidious/yt_backend/extractors.cr4
57 files changed, 910 insertions, 713 deletions
diff --git a/assets/css/default.css b/assets/css/default.css
index 00881253..4d6c6c2f 100644
--- a/assets/css/default.css
+++ b/assets/css/default.css
@@ -197,6 +197,7 @@ img.thumbnail {
display: block; /* See: https://stackoverflow.com/a/11635197 */
width: 100%;
object-fit: cover;
+ aspect-ratio: 16 / 9;
}
.thumbnail-placeholder {
diff --git a/assets/js/notifications.js b/assets/js/notifications.js
index 058553d9..55b7a15c 100644
--- a/assets/js/notifications.js
+++ b/assets/js/notifications.js
@@ -10,7 +10,7 @@ var notifications, delivered;
var notifications_mock = { close: function () { } };
function get_subscriptions() {
- helpers.xhr('GET', '/api/v1/auth/subscriptions?fields=authorId', {
+ helpers.xhr('GET', '/api/v1/auth/subscriptions', {
retries: 5,
entity_name: 'subscriptions'
}, {
@@ -22,7 +22,7 @@ function create_notification_stream(subscriptions) {
// sse.js can't be replaced to EventSource in place as it lack support of payload and headers
// see https://developer.mozilla.org/en-US/docs/Web/API/EventSource/EventSource
notifications = new SSE(
- '/api/v1/auth/notifications?fields=videoId,title,author,authorId,publishedText,published,authorThumbnails,liveNow', {
+ '/api/v1/auth/notifications', {
withCredentials: true,
payload: 'topics=' + subscriptions.map(function (subscription) { return subscription.authorId; }).join(','),
headers: { 'Content-Type': 'application/x-www-form-urlencoded' }
diff --git a/docker-compose.yml b/docker-compose.yml
index 42a5c06b..7e33f6e7 100644
--- a/docker-compose.yml
+++ b/docker-compose.yml
@@ -36,9 +36,6 @@ services:
interval: 30s
timeout: 5s
retries: 2
- depends_on:
- invidious-db:
- condition: service_healthy
invidious-db:
image: docker.io/library/postgres:14
diff --git a/locales/ar.json b/locales/ar.json
index 18298913..57062e89 100644
--- a/locales/ar.json
+++ b/locales/ar.json
@@ -41,7 +41,7 @@
"Time (h:mm:ss):": "الوقت (h:mm:ss):",
"Text CAPTCHA": "نص الكابتشا",
"Image CAPTCHA": "صورة الكابتشا",
- "Sign In": "تسجيل الدخول",
+ "Sign In": "إنشاء حساب",
"Register": "التسجيل",
"E-mail": "البريد الإلكتروني",
"Preferences": "الإعدادات",
@@ -554,5 +554,7 @@
"generic_channels_count_2": "{{count}} قناتان",
"generic_channels_count_3": "{{count}} قنوات",
"generic_channels_count_4": "{{count}} قنوات",
- "generic_channels_count_5": "{{count}} قناة"
+ "generic_channels_count_5": "{{count}} قناة",
+ "Import YouTube watch history (.json)": "استيراد سجل مشاهدة YouTube بصيغة (.json)",
+ "toggle_theme": "تبديل الموضوع"
}
diff --git a/locales/bg.json b/locales/bg.json
index 82591ed8..bcce6a7a 100644
--- a/locales/bg.json
+++ b/locales/bg.json
@@ -486,5 +486,6 @@
"preferences_annotations_label": "Покажи анотаций по подразбиране: ",
"generic_views_count": "{{count}} гледане",
"generic_views_count_plural": "{{count}} гледания",
- "Next page": "Следваща страница"
+ "Next page": "Следваща страница",
+ "Import YouTube watch history (.json)": "Импортиране на историята на гледане от YouTube (.json)"
}
diff --git a/locales/ca.json b/locales/ca.json
index a718eb2b..4ae55804 100644
--- a/locales/ca.json
+++ b/locales/ca.json
@@ -486,5 +486,6 @@
"generic_channels_count_plural": "{{count}} canals",
"generic_button_edit": "Edita",
"generic_button_rss": "RSS",
- "generic_button_delete": "Suprimeix"
+ "generic_button_delete": "Suprimeix",
+ "Import YouTube watch history (.json)": "Importa l'historial de visualitzacions de YouTube (.json)"
}
diff --git a/locales/cs.json b/locales/cs.json
index 10c114eb..4aa20f28 100644
--- a/locales/cs.json
+++ b/locales/cs.json
@@ -503,5 +503,7 @@
"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ů"
+ "generic_channels_count_2": "{{count}} kanálů",
+ "Import YouTube watch history (.json)": "Importovat historii sledování z YouTube (.json)",
+ "toggle_theme": "Přepnout motiv"
}
diff --git a/locales/da.json b/locales/da.json
index 16607546..019f1c51 100644
--- a/locales/da.json
+++ b/locales/da.json
@@ -452,5 +452,40 @@
"crash_page_you_found_a_bug": "Det ser ud til, at du har fundet en fejl i Invidious!",
"crash_page_read_the_faq": "læs <a href=\"`x`\">Ofte stillede spørgsmål (FAQ)</a>",
"crash_page_search_issue": "søgte efter <a href=\"`x`\">eksisterende problemer på GitHub</a>",
- "search_filters_title": "Filter"
+ "search_filters_title": "Filter",
+ "playlist_button_add_items": "Tilføj videoer",
+ "search_message_no_results": "Ingen resultater fundet.",
+ "Import YouTube watch history (.json)": "Importer YouTube afspilningshistorik (.json)",
+ "search_message_change_filters_or_query": "Prøv at udvide din søgeforspørgsel og/eller ændre filtrene.",
+ "search_message_use_another_instance": " Du kan også <a href=\"`x`\">søge på en anden instans</a>.",
+ "Music in this video": "Musik i denne video",
+ "search_filters_date_option_none": "Enhver dato",
+ "search_filters_type_option_all": "Enhver type",
+ "search_filters_duration_option_none": "Enhver varighed",
+ "search_filters_duration_option_medium": "Medium (4 - 20 minutter)",
+ "search_filters_features_option_vr180": "VR180",
+ "generic_channels_count": "{{count}} kanal",
+ "generic_channels_count_plural": "{{count}} kanaler",
+ "Import YouTube playlist (.csv)": "Importer YouTube playliste (.csv)",
+ "Standard YouTube license": "Standard Youtube-licens",
+ "Album: ": "Album: ",
+ "Channel Sponsor": "Kanal-sponsor",
+ "Song: ": "Sang: ",
+ "channel_tab_playlists_label": "Playlister",
+ "channel_tab_channels_label": "Kanaler",
+ "Artist: ": "Kunstner: ",
+ "search_filters_date_label": "Uploaddato",
+ "generic_button_delete": "Slet",
+ "generic_button_edit": "Rediger",
+ "generic_button_save": "Gem",
+ "generic_button_cancel": "Afbryd",
+ "generic_button_rss": "RSS",
+ "Popular enabled: ": "Populær aktiveret: ",
+ "search_filters_apply_button": "Anvend udvalgte filtre",
+ "channel_tab_shorts_label": "Shorts",
+ "channel_tab_streams_label": "Livestreams",
+ "channel_tab_podcasts_label": "Podcasts",
+ "channel_tab_releases_label": "Udgivelser",
+ "Download is disabled": "Download er slået fra",
+ "error_video_not_in_playlist": "Den ønskede video findes ikke i denne playliste. <a href=\"`x`\">Klik her for playlistens startside.</a>"
}
diff --git a/locales/de.json b/locales/de.json
index 59c6a49c..756aff76 100644
--- a/locales/de.json
+++ b/locales/de.json
@@ -148,7 +148,7 @@
"Whitelisted regions: ": "Erlaubte Regionen: ",
"Blacklisted regions: ": "Unerlaubte Regionen: ",
"Shared `x`": "Geteilt `x`",
- "Premieres in `x`": "Zuerst gesehen in `x`",
+ "Premieres in `x`": "Premiere in `x`",
"Premieres `x`": "Erster Start `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.": "Hallo! Anscheinend haben Sie JavaScript deaktiviert. Klicken Sie hier um Kommentare anzuzeigen, beachten sie dass es etwas länger dauern kann um sie zu laden.",
"View YouTube comments": "YouTube Kommentare anzeigen",
@@ -486,5 +486,6 @@
"channel_tab_podcasts_label": "Podcasts",
"channel_tab_releases_label": "Veröffentlichungen",
"generic_channels_count": "{{count}} Kanal",
- "generic_channels_count_plural": "{{count}} Kanäle"
+ "generic_channels_count_plural": "{{count}} Kanäle",
+ "Import YouTube watch history (.json)": "YouTube Wiedergabeverlauf importieren (.json)"
}
diff --git a/locales/en-US.json b/locales/en-US.json
index 29fc7db6..da83767c 100644
--- a/locales/en-US.json
+++ b/locales/en-US.json
@@ -492,5 +492,6 @@
"channel_tab_releases_label": "Releases",
"channel_tab_playlists_label": "Playlists",
"channel_tab_community_label": "Community",
- "channel_tab_channels_label": "Channels"
+ "channel_tab_channels_label": "Channels",
+ "toggle_theme": "Toggle Theme"
}
diff --git a/locales/es.json b/locales/es.json
index 0b8463ea..7a41710e 100644
--- a/locales/es.json
+++ b/locales/es.json
@@ -90,7 +90,7 @@
"preferences_notifications_only_label": "Mostrar solo notificaciones (si hay alguna): ",
"Enable web notifications": "Habilitar notificaciones web",
"`x` uploaded a video": "`x` subió un video",
- "`x` is live": "`x` esta en vivo",
+ "`x` is live": "`x` está en directo",
"preferences_category_data": "Preferencias de los datos",
"Clear watch history": "Borrar el historial de reproducción",
"Import/export data": "Importar/Exportar datos",
@@ -102,7 +102,7 @@
"preferences_category_admin": "Preferencias de administrador",
"preferences_default_home_label": "Página de inicio por defecto: ",
"preferences_feed_menu_label": "Menú de fuentes: ",
- "preferences_show_nick_label": "Mostrar nombre de usuario arriba: ",
+ "preferences_show_nick_label": "Mostrar nombre de usuario encima: ",
"Top enabled: ": "¿Habilitar los destacados? ",
"CAPTCHA enabled: ": "¿Habilitar los CAPTCHA? ",
"Login enabled: ": "¿Habilitar el inicio de sesión? ",
@@ -144,13 +144,13 @@
"License: ": "Licencia: ",
"Family friendly? ": "¿Filtrar contenidos? ",
"Wilson score: ": "Puntuación Wilson: ",
- "Engagement: ": "Compromiso: ",
+ "Engagement: ": "Retención: ",
"Whitelisted regions: ": "Regiones permitidas: ",
"Blacklisted regions: ": "Regiones bloqueadas: ",
"Shared `x`": "Compartido `x`",
"Premieres in `x`": "Se estrena en `x`",
"Premieres `x`": "Estrenos `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.": "¡Hola! Parece que tienes JavaScript desactivado. Haz clic aquí para ver los comentarios, pero tengas en cuenta que pueden tardar un poco más en cargarse.",
+ "Hi! Looks like you have JavaScript turned off. Click here to view comments, keep in mind they may take a bit longer to load.": "¡Hola! Parece que tienes JavaScript desactivado. Haz clic aquí para ver los comentarios, ten en cuenta que pueden tardar un poco más en cargar.",
"View YouTube comments": "Ver los comentarios de YouTube",
"View more comments on Reddit": "Ver más comentarios en Reddit",
"View `x` comments": {
@@ -312,7 +312,7 @@
"Download as: ": "Descargar como: ",
"%A %B %-d, %Y": "%A %B %-d, %Y",
"(edited)": "(editado)",
- "YouTube comment permalink": "Enlace permanente de YouTube del comentario",
+ "YouTube comment permalink": "Enlace permanente de comentario de YouTube",
"permalink": "enlace permanente",
"`x` marked it with a ❤": "`x` lo ha marcado con un ❤",
"Audio mode": "Modo de audio",
@@ -324,10 +324,10 @@
"search_filters_sort_option_rating": "Valoración",
"search_filters_sort_option_date": "Fecha de subida",
"search_filters_sort_option_views": "Visualizaciones",
- "search_filters_type_label": "tipo de contenido",
- "search_filters_duration_label": "duración",
- "search_filters_features_label": "funcionalidades",
- "search_filters_sort_label": "ordenar",
+ "search_filters_type_label": "Tipo de contenido",
+ "search_filters_duration_label": "Duración",
+ "search_filters_features_label": "Funcionalidades",
+ "search_filters_sort_label": "Ordenar",
"search_filters_date_option_hour": "Última hora",
"search_filters_date_option_today": "Hoy",
"search_filters_date_option_week": "Esta semana",
@@ -390,43 +390,58 @@
"search_filters_features_option_three_sixty": "360°",
"videoinfo_watch_on_youTube": "Ver en YouTube",
"preferences_save_player_pos_label": "Guardar posición de reproducción: ",
- "generic_views_count": "{{count}} visualización",
- "generic_views_count_plural": "{{count}} visualizaciones",
- "generic_subscribers_count": "{{count}} suscriptor",
- "generic_subscribers_count_plural": "{{count}} suscriptores",
- "generic_subscriptions_count": "{{count}} suscripción",
- "generic_subscriptions_count_plural": "{{count}} suscripciones",
- "subscriptions_unseen_notifs_count": "{{count}} notificación no vista",
- "subscriptions_unseen_notifs_count_plural": "{{count}} notificaciones no vistas",
- "generic_count_days": "{{count}} día",
- "generic_count_days_plural": "{{count}} días",
- "comments_view_x_replies": "Ver {{count}} respuesta",
- "comments_view_x_replies_plural": "Ver {{count}} respuestas",
- "generic_count_weeks": "{{count}} semana",
- "generic_count_weeks_plural": "{{count}} semanas",
- "generic_playlists_count": "{{count}} lista de reproducción",
- "generic_playlists_count_plural": "{{count}} listas de reproducciones",
- "generic_videos_count": "{{count}} video",
- "generic_videos_count_plural": "{{count}} video",
- "generic_count_months": "{{count}} mes",
- "generic_count_months_plural": "{{count}} meses",
- "comments_points_count": "{{count}} punto",
- "comments_points_count_plural": "{{count}} puntos",
- "generic_count_years": "{{count}} año",
- "generic_count_years_plural": "{{count}} años",
- "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_views_count_0": "{{count}} visualización",
+ "generic_views_count_1": "{{count}} visualizaciones",
+ "generic_views_count_2": "{{count}} visualizaciones",
+ "generic_subscribers_count_0": "{{count}} suscriptor",
+ "generic_subscribers_count_1": "{{count}} suscriptores",
+ "generic_subscribers_count_2": "{{count}} suscriptores",
+ "generic_subscriptions_count_0": "{{count}} suscripción",
+ "generic_subscriptions_count_1": "{{count}} suscripciones",
+ "generic_subscriptions_count_2": "{{count}} suscripciones",
+ "subscriptions_unseen_notifs_count_0": "{{count}} notificación sin ver",
+ "subscriptions_unseen_notifs_count_1": "{{count}} notificaciones sin ver",
+ "subscriptions_unseen_notifs_count_2": "{{count}} notificaciones sin ver",
+ "generic_count_days_0": "{{count}} día",
+ "generic_count_days_1": "{{count}} días",
+ "generic_count_days_2": "{{count}} días",
+ "comments_view_x_replies_0": "Ver {{count}} respuesta",
+ "comments_view_x_replies_1": "Ver {{count}} respuestas",
+ "comments_view_x_replies_2": "Ver {{count}} respuestas",
+ "generic_count_weeks_0": "{{count}} semana",
+ "generic_count_weeks_1": "{{count}} semanas",
+ "generic_count_weeks_2": "{{count}} semanas",
+ "generic_playlists_count_0": "{{count}} lista de reproducción",
+ "generic_playlists_count_1": "{{count}} listas de reproducciones",
+ "generic_playlists_count_2": "{{count}} listas de reproducciones",
+ "generic_videos_count_0": "{{count}} video",
+ "generic_videos_count_1": "{{count}} videos",
+ "generic_videos_count_2": "{{count}} videos",
+ "generic_count_months_0": "{{count}} mes",
+ "generic_count_months_1": "{{count}} meses",
+ "generic_count_months_2": "{{count}} meses",
+ "comments_points_count_0": "{{count}} punto",
+ "comments_points_count_1": "{{count}} puntos",
+ "comments_points_count_2": "{{count}} puntos",
+ "generic_count_years_0": "{{count}} año",
+ "generic_count_years_1": "{{count}} años",
+ "generic_count_years_2": "{{count}} años",
+ "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",
"crash_page_before_reporting": "Antes de notificar un error asegúrate de que has:",
"crash_page_switch_instance": "probado a <a href=\"`x`\">usar otra instancia</a>",
"crash_page_read_the_faq": "leído las <a href=\"`x`\">Preguntas Frecuentes</a>",
"crash_page_search_issue": "buscado <a href=\"`x`\">problemas existentes en GitHub</a>",
"crash_page_you_found_a_bug": "¡Parece que has encontrado un error en Invidious!",
"crash_page_refresh": "probado a <a href=\"`x`\">recargar la página</a>",
- "crash_page_report_issue": "Si nada de lo anterior ha sido de ayuda, por favor, <a href=\"`x`\">abre una nueva incidencia en GitHub</a> (preferiblemente en inglés) e incluye verbatim el siguiente texto en tu mensaje:",
+ "crash_page_report_issue": "Si nada de lo anterior ha sido de ayuda, por favor, <a href=\"`x`\">abre una nueva incidencia en GitHub</a> (preferiblemente en inglés) e incluye el siguiente texto en tu mensaje (NO traduzcas este texto):",
"English (United States)": "Inglés (Estados Unidos)",
"Cantonese (Hong Kong)": "Cantonés (Hong Kong)",
"Dutch (auto-generated)": "Neerlandés (generados automáticamente)",
@@ -454,14 +469,15 @@
"search_message_no_results": "No se han encontrado resultados.",
"search_message_change_filters_or_query": "Pruebe ampliar la consulta de búsqueda y/o a cambiar los filtros.",
"search_filters_title": "Filtros",
- "search_filters_date_label": "fecha de subida",
+ "search_filters_date_label": "Fecha de subida",
"search_filters_date_option_none": "Cualquier fecha",
"search_filters_type_option_all": "Cualquier tipo",
"search_filters_duration_option_none": "Cualquier duración",
"search_filters_features_option_vr180": "VR180",
"search_filters_apply_button": "Aplicar filtros",
- "tokens_count": "{{count}} token",
- "tokens_count_plural": "{{count}} tokens",
+ "tokens_count_0": "{{count}} token",
+ "tokens_count_1": "{{count}} tokens",
+ "tokens_count_2": "{{count}} tokens",
"search_message_use_another_instance": " También puede <a href=\"`x`\">buscar en otra instancia</a>.",
"Popular enabled: ": "¿Habilitar la sección popular? ",
"error_video_not_in_playlist": "El video que solicitaste no existe en esta lista de reproducción. <a href=\"`x`\">Haz clic aquí para acceder a la página de inicio de la lista de reproducción.</a>",
@@ -485,6 +501,9 @@
"generic_button_rss": "RSS",
"channel_tab_podcasts_label": "Podcasts",
"channel_tab_releases_label": "Publicaciones",
- "generic_channels_count": "{{count}} canal",
- "generic_channels_count_plural": "{{count}} canales"
+ "generic_channels_count_0": "{{count}} canal",
+ "generic_channels_count_1": "{{count}} canales",
+ "generic_channels_count_2": "{{count}} canales",
+ "Import YouTube watch history (.json)": "Importar el historial de las visualizaciones de YouTube (.json)",
+ "toggle_theme": "Alternar tema"
}
diff --git a/locales/fa.json b/locales/fa.json
index 9b6c625d..d0251201 100644
--- a/locales/fa.json
+++ b/locales/fa.json
@@ -1,9 +1,14 @@
{
- "generic_views_count_0": "{{count}} بازدید",
- "generic_videos_count_0": "{{count}} ویدئو",
- "generic_playlists_count_0": "{{count}} فهرست پخش",
- "generic_subscribers_count_0": "{{count}} دنبال کننده",
- "generic_subscriptions_count_0": "{{count}} اشتراک ها",
+ "generic_views_count": "{{count}} بازدید",
+ "generic_views_count_plural": "{{count}} بازدید",
+ "generic_videos_count": "{{count}} ویدئو",
+ "generic_videos_count_plural": "{{count}} ویدئو",
+ "generic_playlists_count": "{{count}} فهرست پخش",
+ "generic_playlists_count_plural": "{{count}} فهرست پخش",
+ "generic_subscribers_count": "{{count}} دنبال کننده",
+ "generic_subscribers_count_plural": "{{count}} دنبال کننده",
+ "generic_subscriptions_count": "{{count}} اشتراک",
+ "generic_subscriptions_count_plural": "{{count}} اشتراک",
"LIVE": "زنده",
"Shared `x` ago": "`x` پیش به اشتراک گذاشته شده",
"Unsubscribe": "لغو اشتراک",
@@ -117,13 +122,15 @@
"Subscription manager": "مدیریت اشتراک",
"Token manager": "مدیر توکن",
"Token": "توکن",
- "tokens_count_0": "{{count}} توکن ها",
+ "tokens_count": "{{count}} توکن",
+ "tokens_count_plural": "{{count}} توکن",
"Import/export": "وارد کردن/خارج کردن",
"unsubscribe": "لغو اشتراک",
"revoke": "ابطال",
"Subscriptions": "اشتراک ها",
- "subscriptions_unseen_notifs_count_0": "{{count}} اعلان نادیده",
- "search": "جستجو",
+ "subscriptions_unseen_notifs_count": "{{count}} اعلان نادیده",
+ "subscriptions_unseen_notifs_count_plural": "{{count}} اعلان نادیده",
+ "search": "جست و جو",
"Log out": "خروج",
"Released under the AGPLv3 on Github.": "منتشر شده تحت پروانه AGPLv3 روی گیت‌هاب.",
"Source available here.": "منبع اینجا دردسترس است.",
@@ -183,10 +190,12 @@
"This channel does not exist.": "این کانال وجود ندارد.",
"Could not get channel info.": "نمیتوان اطلاعات کانال را دریافت کرد.",
"Could not fetch comments": "نمیتوان نظرات را دریافت کرد",
- "comments_view_x_replies_0": "نمایش {{count}} پاسخ ها",
+ "comments_view_x_replies": "نمایش {{count}} پاسخ",
+ "comments_view_x_replies_plural": "نمایش {{count}} پاسخ",
"`x` ago": "`x` پیش",
"Load more": "بارگذاری بیشتر",
- "comments_points_count_0": "{{count}} نقطه ها",
+ "comments_points_count": "{{count}} نقطه",
+ "comments_points_count_plural": "{{count}} نقطه",
"Could not create mix.": "نمیتوان میکس ساخت.",
"Empty playlist": "سیاههٔ پخش خالی",
"Not a playlist.": "یک سیاههٔ پخش نیست.",
@@ -304,16 +313,23 @@
"Yiddish": "ییدیش",
"Yoruba": "یوروبایی",
"Zulu": "زولو",
- "generic_count_years_0": "{{count}} سال",
- "generic_count_months_0": "{{count}} ماه",
- "generic_count_weeks_0": "{{count}} هفته",
- "generic_count_days_0": "{{count}} روز",
- "generic_count_hours_0": "{{count}} ساعت",
- "generic_count_minutes_0": "{{count}} دقیقه",
- "generic_count_seconds_0": "{{count}} ثانیه",
+ "generic_count_years": "{{count}} سال",
+ "generic_count_years_plural": "{{count}} سال",
+ "generic_count_months": "{{count}} ماه",
+ "generic_count_months_plural": "{{count}} ماه",
+ "generic_count_weeks": "{{count}} هفته",
+ "generic_count_weeks_plural": "{{count}} هفته",
+ "generic_count_days": "{{count}} روز",
+ "generic_count_days_plural": "{{count}} روز",
+ "generic_count_hours": "{{count}} ساعت",
+ "generic_count_hours_plural": "{{count}} ساعت",
+ "generic_count_minutes": "{{count}} دقیقه",
+ "generic_count_minutes_plural": "{{count}} دقیقه",
+ "generic_count_seconds": "{{count}} ثانیه",
+ "generic_count_seconds_plural": "{{count}} ثانیه",
"Fallback comments: ": "نظرات عقب گرد: ",
"Popular": "محبوب",
- "Search": "جستجو",
+ "Search": "جست و جو",
"Top": "بالا",
"About": "درباره",
"Rating: ": "رتبه دهی: ",
@@ -445,5 +461,28 @@
"Song: ": "آهنگ: ",
"Channel Sponsor": "اسپانسر کانال",
"Standard YouTube license": "پروانه استاندارد YouTube",
- "search_message_use_another_instance": " شما همچنین می‌توانید <a href=\"`x`\">در نمونه دیگر هم جستجو کنید</a>."
+ "search_message_use_another_instance": " شما همچنین می‌توانید <a href=\"`x`\">در نمونه دیگر هم جستجو کنید</a>.",
+ "Download is disabled": "دریافت غیرفعال است",
+ "crash_page_before_reporting": "پیش از گزارش ایراد، مطمئنید شوید که:",
+ "playlist_button_add_items": "افزودن ویدیو",
+ "user_saved_playlists": "فهرست‌های پخش ذخیره شده",
+ "crash_page_refresh": "که صفحه را <a href=\"`x`\">بازنشانی</a> کرده‌اید",
+ "generic_button_save": "ذخیره",
+ "generic_button_cancel": "لغو",
+ "generic_channels_count": "{{count}} کانال",
+ "generic_channels_count_plural": "{{count}} کانال",
+ "generic_button_edit": "ویرایش",
+ "crash_page_switch_instance": "که تلاش کرده‌اید <a href=\"`x`\">از یک نمونهٔ دیگر</a> استفاده کنید",
+ "generic_button_rss": "خوراک RSS",
+ "crash_page_read_the_faq": "که <a href=\"`x`\">سوالات بیشتر پرسیده شده (FAQ)</a> را خوانده‌اید",
+ "generic_button_delete": "حذف",
+ "Import YouTube playlist (.csv)": "واردکردن فهرست‌پخش YouTube (.csv)",
+ "Import YouTube watch history (.json)": "وارد کردن فهرست پخش YouTube (.json)",
+ "crash_page_you_found_a_bug": "به نظر می‌رسد که ایرادی در Invidious پیدا کرده‌اید!",
+ "channel_tab_podcasts_label": "پادکست‌ها",
+ "channel_tab_streams_label": "پخش زنده‌ها",
+ "channel_tab_shorts_label": "Shortها",
+ "channel_tab_playlists_label": "فهرست‌های پخش",
+ "channel_tab_channels_label": "کانال‌ها",
+ "error_video_not_in_playlist": "ویدیوی درخواستی معلق به این فهرست پخش نیست. <a href=\"`x`\">کلیک کنید تا به صفحهٔ اصلی فهرست پخش بروید.</a>"
}
diff --git a/locales/fi.json b/locales/fi.json
index 5d8578a5..14c2b0fc 100644
--- a/locales/fi.json
+++ b/locales/fi.json
@@ -14,7 +14,7 @@
"Clear watch history?": "Tyhjennä katseluhistoria?",
"New password": "Uusi salasana",
"New passwords must match": "Uusien salasanojen täytyy täsmätä",
- "Authorize token?": "Valuutetaanko tunnus?",
+ "Authorize token?": "Valtuutetaanko tunnus?",
"Authorize token for `x`?": "Valtuutetaanko tunnus `x`:lle?",
"Yes": "Kyllä",
"No": "Ei",
diff --git a/locales/fr.json b/locales/fr.json
index 772c81c8..251e88bc 100644
--- a/locales/fr.json
+++ b/locales/fr.json
@@ -503,5 +503,6 @@
"Download is disabled": "Le téléchargement est désactivé",
"Import YouTube playlist (.csv)": "Importer des listes de lecture de Youtube (.csv)",
"channel_tab_releases_label": "Parutions",
- "channel_tab_podcasts_label": "Émissions audio"
+ "channel_tab_podcasts_label": "Émissions audio",
+ "Import YouTube watch history (.json)": "Importer l'historique de visionnement YouTube (.json)"
}
diff --git a/locales/hi.json b/locales/hi.json
index 21807c50..a7e0639a 100644
--- a/locales/hi.json
+++ b/locales/hi.json
@@ -476,7 +476,7 @@
"generic_button_cancel": "रद्द करें",
"generic_button_rss": "आरएसएस",
"generic_button_edit": "संपादित करें",
- "generic_button_delete": "मिटाएं",
+ "generic_button_delete": "हटाएं",
"playlist_button_add_items": "वीडियो जोड़ें",
"Song: ": "गाना: ",
"channel_tab_podcasts_label": "पाॅडकास्ट",
@@ -484,5 +484,8 @@
"Import YouTube playlist (.csv)": "YouTube प्लेलिस्ट (.csv) आयात करें",
"Standard YouTube license": "मानक यूट्यूब लाइसेंस",
"Channel Sponsor": "चैनल प्रायोजक",
- "Download is disabled": "डाउनलोड करना अक्षम है"
+ "Download is disabled": "डाउनलोड करना अक्षम है",
+ "generic_channels_count": "{{count}} चैनल",
+ "generic_channels_count_plural": "{{count}} चैनल",
+ "Import YouTube watch history (.json)": "YouTube पर देखने का इतिहास आयात करें (.json)"
}
diff --git a/locales/hr.json b/locales/hr.json
index ef931202..2d86144f 100644
--- a/locales/hr.json
+++ b/locales/hr.json
@@ -503,5 +503,6 @@
"channel_tab_releases_label": "Izdanja",
"generic_channels_count_0": "{{count}} kanal",
"generic_channels_count_1": "{{count}} kanala",
- "generic_channels_count_2": "{{count}} kanala"
+ "generic_channels_count_2": "{{count}} kanala",
+ "Import YouTube watch history (.json)": "Uvezi YouTube povijest gledanja (.json)"
}
diff --git a/locales/ia.json b/locales/ia.json
new file mode 100644
index 00000000..19b6b0c0
--- /dev/null
+++ b/locales/ia.json
@@ -0,0 +1,41 @@
+{
+ "New password": "Nove contrasigno",
+ "preferences_player_style_label": "Stylo de reproductor: ",
+ "preferences_region_label": "Pais de contento: ",
+ "oldest": "plus ancian",
+ "published": "data de publication",
+ "invidious": "Invidious",
+ "Image CAPTCHA": "Imagine CAPTCHA",
+ "newest": "plus nove",
+ "generic_button_save": "Salvar",
+ "Dark mode: ": "Modo obscur: ",
+ "preferences_dark_mode_label": "Thema: ",
+ "preferences_category_subscription": "Preferentias de subscription",
+ "last": "ultime",
+ "generic_button_cancel": "Cancellar",
+ "popular": "popular",
+ "Time (h:mm:ss):": "Tempore (h:mm:ss):",
+ "preferences_autoplay_label": "Reproduction automatic: ",
+ "Sign In": "Aperir le session",
+ "Log in": "Initiar le session",
+ "preferences_speed_label": "Velocitate per predefinition: ",
+ "preferences_comments_label": "Commentos predefinite: ",
+ "light": "clar",
+ "No": "Non",
+ "youtube": "YouTube",
+ "LIVE": "IN DIRECTE",
+ "reddit": "Reddit",
+ "preferences_category_player": "Preferentias de reproductor",
+ "Preferences": "Preferentias",
+ "preferences_quality_dash_option_auto": "Automatic",
+ "dark": "obscur",
+ "generic_button_rss": "RSS",
+ "Export": "Exportar",
+ "History": "Chronologia",
+ "Password": "Contrasigno",
+ "User ID": "ID de usator",
+ "E-mail": "E-mail",
+ "Delete account?": "Deler conto?",
+ "preferences_volume_label": "Volumine del reproductor: ",
+ "preferences_sort_label": "Ordinar le videos per: "
+}
diff --git a/locales/id.json b/locales/id.json
index 8961880b..4c6e8548 100644
--- a/locales/id.json
+++ b/locales/id.json
@@ -469,5 +469,6 @@
"error_video_not_in_playlist": "Video yang diminta tidak ada dalam daftar putar ini. <a href=\"`x`\">Klik di sini untuk halaman beranda daftar putar.</a>",
"generic_button_delete": "Hapus",
"Import YouTube playlist (.csv)": "Impor daftar putar YouTube (.csv)",
- "Standard YouTube license": "Lisensi YouTube standar"
+ "Standard YouTube license": "Lisensi YouTube standar",
+ "Import YouTube watch history (.json)": "Impor riwayat tontonan YouTube (.json)"
}
diff --git a/locales/it.json b/locales/it.json
index 7e1b12c6..7b6bb5d9 100644
--- a/locales/it.json
+++ b/locales/it.json
@@ -503,5 +503,6 @@
"channel_tab_podcasts_label": "Podcast",
"generic_channels_count_0": "{{count}} canale",
"generic_channels_count_1": "{{count}} canali",
- "generic_channels_count_2": "{{count}} canali"
+ "generic_channels_count_2": "{{count}} canali",
+ "Import YouTube watch history (.json)": "Importa la cronologia delle visualizzazioni di YouTube (.json)"
}
diff --git a/locales/ja.json b/locales/ja.json
index 17e60998..2e3437bc 100644
--- a/locales/ja.json
+++ b/locales/ja.json
@@ -53,7 +53,7 @@
"preferences_category_player": "プレイヤーの設定",
"preferences_video_loop_label": "常にループ: ",
"preferences_autoplay_label": "自動再生: ",
- "preferences_continue_label": "次の動画を自動再生: ",
+ "preferences_continue_label": "次の動画に移動: ",
"preferences_continue_autoplay_label": "次の動画を自動再生: ",
"preferences_listen_label": "音声モードを使用: ",
"preferences_local_label": "動画視聴にプロキシを経由: ",
@@ -68,7 +68,7 @@
"preferences_related_videos_label": "関連動画を表示: ",
"preferences_annotations_label": "最初からアノテーションを表示: ",
"preferences_extend_desc_label": "動画の説明文を自動的に拡張: ",
- "preferences_vr_mode_label": "対話的な360°動画 (WebGL が必要): ",
+ "preferences_vr_mode_label": "対話的な360°動画 (WebGLが必要): ",
"preferences_category_visual": "外観設定",
"preferences_player_style_label": "プレイヤーのスタイル: ",
"Dark mode: ": "ダークモード: ",
@@ -125,9 +125,9 @@
"subscriptions_unseen_notifs_count_0": "{{count}}件の未読通知",
"search": "検索",
"Log out": "ログアウト",
- "Released under the AGPLv3 on Github.": "GitHub 上で AGPLv3 の元で公開",
+ "Released under the AGPLv3 on Github.": "GitHub上でAGPLv3の元で公開",
"Source available here.": "ソースはここで閲覧可能です。",
- "View JavaScript license information.": "JavaScript ライセンス情報",
+ "View JavaScript license information.": "JavaScriptライセンス情報",
"View privacy policy.": "個人情報保護方針",
"Trending": "急上昇",
"Public": "公開",
@@ -144,7 +144,7 @@
"Show more": "もっと見る",
"Show less": "表示を少なく",
"Watch on YouTube": "YouTubeで視聴",
- "Switch Invidious Instance": "Invidious インスタンスの変更",
+ "Switch Invidious Instance": "Invidiousインスタンスの変更",
"Hide annotations": "アノテーションを隠す",
"Show annotations": "アノテーションを表示",
"Genre: ": "ジャンル: ",
@@ -363,9 +363,9 @@
"search_filters_features_option_location": "場所",
"search_filters_features_option_hdr": "HDR",
"Current version: ": "現在のバージョン: ",
- "next_steps_error_message": "下記のものを試して下さい: ",
- "next_steps_error_message_refresh": "再読込",
- "next_steps_error_message_go_to_youtube": "YouTubeへ",
+ "next_steps_error_message": "以下をお試してください: ",
+ "next_steps_error_message_refresh": "再読み込み",
+ "next_steps_error_message_go_to_youtube": "YouTubeを開く",
"search_filters_duration_option_short": "4分未満",
"footer_documentation": "説明書",
"footer_source_code": "ソースコード",
@@ -459,7 +459,7 @@
"Song: ": "曲: ",
"Channel Sponsor": "チャンネルのスポンサー",
"Standard YouTube license": "標準 Youtube ライセンス",
- "Download is disabled": "ダウンロード: このインスタンスでは未対応",
+ "Download is disabled": "ダウンロード: このインスタンスは未対応",
"Import YouTube playlist (.csv)": "YouTube 再生リストをインポート (.csv)",
"generic_button_delete": "削除",
"generic_button_cancel": "キャンセル",
@@ -469,5 +469,6 @@
"generic_button_save": "保存",
"generic_button_rss": "RSS",
"playlist_button_add_items": "動画を追加",
- "generic_channels_count_0": "{{count}}個のチャンネル"
+ "generic_channels_count_0": "{{count}}個のチャンネル",
+ "Import YouTube watch history (.json)": "YouTube 視聴履歴をインポート (.json)"
}
diff --git a/locales/ko.json b/locales/ko.json
index e496bd2a..c0257ee5 100644
--- a/locales/ko.json
+++ b/locales/ko.json
@@ -46,7 +46,7 @@
"source": "출처",
"JavaScript license information": "자바스크립트 라이선스 정보",
"An alternative front-end to YouTube": "유튜브의 프론트엔드 대안",
- "History": "역사",
+ "History": "시청 기록",
"Delete account?": "계정을 삭제 하시겠습니까?",
"Export data as JSON": "JSON으로 데이터 내보내기",
"Export subscriptions as OPML (for NewPipe & FreeTube)": "OPML로 구독 내보내기 (뉴파이프 및 프리튜브)",
@@ -351,7 +351,7 @@
"News": "뉴스",
"Gaming": "게임",
"Music": "음악",
- "Default": "디폴트",
+ "Default": "전체",
"Rating: ": "평점: ",
"About": "정보",
"Top": "최고",
@@ -469,5 +469,6 @@
"generic_button_cancel": "취소",
"generic_button_rss": "RSS",
"channel_tab_releases_label": "출시",
- "generic_channels_count_0": "{{count}} 채널"
+ "generic_channels_count_0": "{{count}} 채널",
+ "Import YouTube watch history (.json)": "유튜브 시청 기록 가져오기 (.json)"
}
diff --git a/locales/nb-NO.json b/locales/nb-NO.json
index 08b1e0e2..cf0ee286 100644
--- a/locales/nb-NO.json
+++ b/locales/nb-NO.json
@@ -486,5 +486,6 @@
"generic_button_rss": "RSS",
"playlist_button_add_items": "Legg til videoer",
"generic_channels_count": "{{count}} kanal",
- "generic_channels_count_plural": "{{count}} kanaler"
+ "generic_channels_count_plural": "{{count}} kanaler",
+ "Import YouTube watch history (.json)": "Importere YouTube visningshistorikk (.json)"
}
diff --git a/locales/nl.json b/locales/nl.json
index aa5da731..a30bc5b5 100644
--- a/locales/nl.json
+++ b/locales/nl.json
@@ -107,10 +107,10 @@
"Report statistics: ": "Statistieken bijhouden? ",
"Save preferences": "Instellingen opslaan",
"Subscription manager": "Abonnementen beheren",
- "Token manager": "Toegangssleutels beheren",
+ "Token manager": "Toegangssleutelbeheerder",
"Token": "Toegangssleutel",
"Import/export": "Importeren/Exporteren",
- "unsubscribe": "Deabonneren",
+ "unsubscribe": "deabonneren",
"revoke": "Intrekken",
"Subscriptions": "Abonnementen",
"search": "zoeken",
@@ -357,7 +357,7 @@
"footer_original_source_code": "Originele bron-code",
"footer_modfied_source_code": "Gewijzigde bron-code",
"adminprefs_modified_source_code_url_label": "URL naar gewijzigde bron-code-opslagplaats",
- "next_steps_error_message": "Waarna u moet proberen om: ",
+ "next_steps_error_message": "Daarna moet u proberen om: ",
"footer_source_code": "Bron-code",
"search_filters_duration_option_long": "Lang (> 20 minuten)",
"preferences_quality_option_dash": "DASH (adaptieve kwaliteit)",
@@ -462,5 +462,30 @@
"Spanish (auto-generated)": "Spaans (automatisch gegenereerd)",
"crash_page_you_found_a_bug": "Je lijkt een bug in Invidious tegengekomen te zijn!",
"search_filters_duration_option_medium": "Gemiddeld (4 - 20 minuten)",
- "crash_page_report_issue": "Indien het bovenstaande niet hielp, gelieve dan <a href=\"`x`\">een nieuw ticket op GitHub</a> te openen (liefst in het Engels) en neem de volgende tekst op in je bericht (gelieve deze NIET te vertalen):"
+ "crash_page_report_issue": "Indien het bovenstaande niet hielp, gelieve dan <a href=\"`x`\">een nieuw ticket op GitHub</a> te openen (liefst in het Engels) en neem de volgende tekst op in je bericht (gelieve deze NIET te vertalen):",
+ "channel_tab_podcasts_label": "Podcasts",
+ "Download is disabled": "Downloaden is uitgeschakeld",
+ "Channel Sponsor": "Kanaalsponsor",
+ "channel_tab_streams_label": "Livestreams",
+ "playlist_button_add_items": "Video's toevoegen",
+ "Artist: ": "Artiest: ",
+ "generic_button_save": "Opslaan",
+ "generic_button_cancel": "Annuleren",
+ "Album: ": "Album: ",
+ "channel_tab_shorts_label": "Shorts",
+ "channel_tab_releases_label": "Uitgaves",
+ "Song: ": "Lied: ",
+ "generic_channels_count": "{{count}} kanaal",
+ "generic_channels_count_plural": "{{count}} kanalen",
+ "Popular enabled: ": "Populair geactiveerd: ",
+ "channel_tab_playlists_label": "Afspeellijsten",
+ "generic_button_edit": "Bewerken",
+ "Music in this video": "Muziek in deze video",
+ "generic_button_rss": "RSS",
+ "channel_tab_channels_label": "Kanalen",
+ "error_video_not_in_playlist": "De gevraagde video bestaat niet in deze afspeellijst. <a href=\"`x`\">Klik hier voor de startpagina van de afspeellijst.</a>",
+ "generic_button_delete": "Verwijderen",
+ "Import YouTube playlist (.csv)": "YouTube-afspeellijst importeren (.csv)",
+ "Standard YouTube license": "Standaard YouTube-licentie",
+ "Import YouTube watch history (.json)": "YouTube-kijkgeschiedenis importeren (.json)"
}
diff --git a/locales/pl.json b/locales/pl.json
index 313f11cb..0d18e90a 100644
--- a/locales/pl.json
+++ b/locales/pl.json
@@ -492,7 +492,7 @@
"Song: ": "Piosenka: ",
"Channel Sponsor": "Sponsor kanału",
"Standard YouTube license": "Standardowa licencja YouTube",
- "Import YouTube playlist (.csv)": "Importuj playlistę YouTube (.csv)",
+ "Import YouTube playlist (.csv)": "Importuj playlistę z YouTube (.csv)",
"generic_button_edit": "Edytuj",
"generic_button_cancel": "Anuluj",
"generic_button_rss": "RSS",
@@ -503,5 +503,7 @@
"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"
+ "generic_channels_count_2": "{{count}} kanałów",
+ "Import YouTube watch history (.json)": "Importuj historię oglądania z YouTube (.json)",
+ "toggle_theme": "Przełącz motyw"
}
diff --git a/locales/pt-BR.json b/locales/pt-BR.json
index 1e089723..af14eb29 100644
--- a/locales/pt-BR.json
+++ b/locales/pt-BR.json
@@ -503,5 +503,7 @@
"generic_button_rss": "RSS",
"generic_channels_count_0": "{{count}} canal",
"generic_channels_count_1": "{{count}} canais",
- "generic_channels_count_2": "{{count}} canais"
+ "generic_channels_count_2": "{{count}} canais",
+ "Import YouTube watch history (.json)": "Importar histórico de reprodução do YouTube (.json)",
+ "toggle_theme": "Alternar Tema"
}
diff --git a/locales/pt.json b/locales/pt.json
index e7cc4810..c1d8b5b4 100644
--- a/locales/pt.json
+++ b/locales/pt.json
@@ -1,7 +1,7 @@
{
- "search_filters_type_option_show": "Espetáculo",
+ "search_filters_type_option_show": "Série",
"search_filters_sort_option_views": "Visualizações",
- "search_filters_sort_option_date": "Data de envio",
+ "search_filters_sort_option_date": "Data de carregamento",
"search_filters_sort_option_rating": "Avaliação",
"search_filters_sort_option_relevance": "Relevância",
"Switch Invidious Instance": "Mudar a instância do Invidious",
@@ -13,7 +13,7 @@
"preferences_category_misc": "Preferências diversas",
"preferences_vr_mode_label": "Vídeos interativos de 360 graus (necessita de WebGL): ",
"preferences_extend_desc_label": "Estender automaticamente a descrição do vídeo: ",
- "next_steps_error_message_go_to_youtube": "Ir ao YouTube",
+ "next_steps_error_message_go_to_youtube": "Ir para o YouTube",
"next_steps_error_message": "Pode tentar as seguintes opções: ",
"next_steps_error_message_refresh": "Atualizar",
"search_filters_features_option_hdr": "HDR",
@@ -44,20 +44,27 @@
"Default": "Predefinido",
"Top": "Destaques",
"Search": "Pesquisar",
- "generic_count_years": "{{count}} segundo",
- "generic_count_years_plural": "{{count}} segundos",
- "generic_count_months": "{{count}} minuto",
- "generic_count_months_plural": "{{count}} minutos",
- "generic_count_weeks": "{{count}} hora",
- "generic_count_weeks_plural": "{{count}} horas",
- "generic_count_days": "{{count}} dia",
- "generic_count_days_plural": "{{count}} dias",
- "generic_count_hours": "{{count}} seman",
- "generic_count_hours_plural": "{{count}} semanas",
- "generic_count_minutes": "{{count}} mês",
- "generic_count_minutes_plural": "{{count}} meses",
- "generic_count_seconds": "{{count}} ano",
- "generic_count_seconds_plural": "{{count}} anos",
+ "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",
"Chinese (Traditional)": "Chinês (tradicional)",
"Chinese (Simplified)": "Chinês (simplificado)",
"Could not pull trending pages.": "Não foi possível obter as páginas de tendências.",
@@ -75,7 +82,7 @@
"Import/export data": "Importar / exportar dados",
"preferences_annotations_label": "Mostrar anotações sempre: ",
"preferences_continue_label": "Reproduzir sempre o próximo: ",
- "Sign In": "Iniciar sessão",
+ "Sign In": "Entrar",
"Log in/register": "Iniciar sessão/registar",
"Delete account?": "Eliminar conta?",
"Import and Export Data": "Importar e exportar dados",
@@ -167,8 +174,9 @@
"Log out": "Terminar sessão",
"Subscriptions": "Subscrições",
"revoke": "revogar",
- "tokens_count": "{{count}} token",
- "tokens_count_plural": "{{count}} tokens",
+ "tokens_count_0": "{{count}} Token",
+ "tokens_count_1": "{{count}} Tokens",
+ "tokens_count_2": "{{count}} Tokens",
"Token": "Token",
"Token manager": "Gerir tokens",
"Subscription manager": "Gerir subscrições",
@@ -402,31 +410,39 @@
"videoinfo_youTube_embed_link": "Incorporar",
"preferences_save_player_pos_label": "Guardar a posição de reprodução atual do vídeo: ",
"download_subtitles": "Legendas - `x` (.vtt)",
- "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",
"videoinfo_started_streaming_x_ago": "Iniciou a transmissão há `x`",
"user_saved_playlists": "`x` listas de reprodução guardadas",
- "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",
- "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",
- "generic_subscribers_count": "{{count}} inscrito",
- "generic_subscribers_count_plural": "{{count}} inscritos",
- "generic_subscriptions_count": "{{count}} inscrição",
- "generic_subscriptions_count_plural": "{{count}} inscrições",
- "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",
+ "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",
+ "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",
+ "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 encontrou um erro no Invidious!",
"crash_page_before_reporting": "Antes de reportar um erro, verifique se:",
"crash_page_refresh": "tentou <a href=\"`x`\">recarregar a página</a>",
"crash_page_switch_instance": "tentou <a href=\"`x`\">usar outra instância</a>",
"crash_page_read_the_faq": "leia as <a href=\"`x`\">Perguntas frequentes (FAQ)</a>",
"crash_page_search_issue": "procurou se <a href=\"`x`\">o erro já foi reportado no GitHub</a>",
- "crash_page_report_issue": "Se nenhuma opção acima ajudou, por favor <a href=\"`x`\">abra um novo problema no Github</a> (preferencialmente em inglês) e inclua o seguinte texto tal qual (NÃO o traduza):",
+ "crash_page_report_issue": "Se nenhuma opção acima ajudou, por favor <a href=\"`x`\">abra um novo problema no Github</a> (preferencialmente em inglês) e inclua o seguinte texto (NÃO o traduza):",
"user_created_playlists": "`x` listas de reprodução criadas",
"search_filters_title": "Filtro",
"Chinese (Taiwan)": "Chinês (Taiwan)",
@@ -464,7 +480,7 @@
"search_filters_type_option_all": "Qualquer tipo",
"search_filters_duration_option_none": "Qualquer duração",
"Popular enabled: ": "Página \"popular\" ativada: ",
- "error_video_not_in_playlist": "O vídeo pedido não existe nesta lista de reprodução. <a href=\"`x`\">Clique aqui para a página inicial da lista de reprodução.</a>",
+ "error_video_not_in_playlist": "O vídeo pedido não existe nesta lista de reprodução. <a href=\"`x`\">Clique aqui para voltar à página inicial da lista de reprodução.</a>",
"channel_tab_playlists_label": "Listas de reprodução",
"channel_tab_channels_label": "Canais",
"channel_tab_shorts_label": "Curtos",
@@ -484,5 +500,10 @@
"channel_tab_releases_label": "Lançamentos",
"generic_button_save": "Salvar",
"generic_button_cancel": "Cancelar",
- "playlist_button_add_items": "Adicionar vídeos"
+ "playlist_button_add_items": "Adicionar vídeos",
+ "generic_channels_count_0": "{{count}} canal",
+ "generic_channels_count_1": "{{count}} canais",
+ "generic_channels_count_2": "{{count}} canais",
+ "Import YouTube watch history (.json)": "Importar histórico de reprodução do YouTube (.json)",
+ "toggle_theme": "Trocar tema"
}
diff --git a/locales/ru.json b/locales/ru.json
index 2769f3ab..61bf9e92 100644
--- a/locales/ru.json
+++ b/locales/ru.json
@@ -8,14 +8,14 @@
"newest": "сначала новые",
"oldest": "сначала старые",
"popular": "популярные",
- "last": "недавние",
+ "last": "последние",
"Next page": "Следующая страница",
"Previous page": "Предыдущая страница",
"Clear watch history?": "Очистить историю просмотров?",
"New password": "Новый пароль",
"New passwords must match": "Новые пароли не совпадают",
"Authorize token?": "Авторизовать токен?",
- "Authorize token for `x`?": "Авторизовать токен для `x`?",
+ "Authorize token for `x`?": "Токен авторизации для `x`?",
"Yes": "Да",
"No": "Нет",
"Import and Export Data": "Импорт и экспорт данных",
@@ -29,7 +29,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",
@@ -42,7 +42,7 @@
"Text CAPTCHA": "Текстовая капча (англ.)",
"Image CAPTCHA": "Капча-картинка",
"Sign In": "Войти",
- "Register": "Зарегистрироваться",
+ "Register": "Регистрация",
"E-mail": "Эл. почта",
"Preferences": "Настройки",
"preferences_category_player": "Настройки проигрывателя",
@@ -61,7 +61,7 @@
"preferences_captions_label": "Основной язык субтитров: ",
"Fallback captions: ": "Дополнительный язык субтитров: ",
"preferences_related_videos_label": "Показывать похожие видео? ",
- "preferences_annotations_label": "Всегда показывать аннотации? ",
+ "preferences_annotations_label": "Показывать аннотации по умолчанию: ",
"preferences_extend_desc_label": "Автоматически раскрывать описание видео: ",
"preferences_vr_mode_label": "Интерактивные 360-градусные видео (необходим WebGL): ",
"preferences_category_visual": "Настройки сайта",
@@ -77,13 +77,13 @@
"preferences_annotations_subscribed_label": "Всегда показывать аннотации на каналах из ваших подписок? ",
"Redirect homepage to feed: ": "Показывать подписки на главной странице: ",
"preferences_max_results_label": "Число видео в ленте: ",
- "preferences_sort_label": "Сортировать видео: ",
- "published": "по дате публикации",
- "published - reverse": "по дате публикации в обратном порядке",
- "alphabetically": "по алфавиту",
- "alphabetically - reverse": "по алфавиту в обратном порядке",
- "channel name": "по названию канала",
- "channel name - reverse": "по названию канала в обратном порядке",
+ "preferences_sort_label": "Сортировать видео по: ",
+ "published": "дате публикации",
+ "published - reverse": "дате публикации в обратном порядке",
+ "alphabetically": "алфавиту",
+ "alphabetically - reverse": "алфавиту в обратном порядке",
+ "channel name": "названию канала",
+ "channel name - reverse": "названию канала в обратном порядке",
"Only show latest video from channel: ": "Показывать только последние видео с каналов: ",
"Only show latest unwatched video from channel: ": "Показывать только последние непросмотренные видео с канала: ",
"preferences_unseen_only_label": "Показывать только непросмотренные видео: ",
@@ -134,8 +134,8 @@
"Title": "Заголовок",
"Playlist privacy": "Видимость плейлиста",
"Editing playlist `x`": "Редактирование плейлиста `x`",
- "Show more": "Развернуть",
- "Show less": "Свернуть",
+ "Show more": "Показать больше",
+ "Show less": "Показать меньше",
"Watch on YouTube": "Смотреть на YouTube",
"Switch Invidious Instance": "Сменить зеркало Invidious",
"Hide annotations": "Скрыть аннотации",
@@ -414,7 +414,7 @@
"generic_count_days_0": "{{count}} день",
"generic_count_days_1": "{{count}} дня",
"generic_count_days_2": "{{count}} дней",
- "preferences_quality_dash_option_auto": "Автоматическое",
+ "preferences_quality_dash_option_auto": "Авто",
"preferences_quality_dash_option_1080p": "1080p",
"preferences_quality_dash_option_720p": "720p",
"generic_subscriptions_count_0": "{{count}} подписка",
@@ -466,7 +466,7 @@
"search_filters_features_option_three_sixty": "360°",
"Video unavailable": "Видео недоступно",
"preferences_save_player_pos_label": "Запоминать позицию: ",
- "preferences_region_label": "Страна: ",
+ "preferences_region_label": "Страна источник ",
"preferences_watch_history_label": "Включить историю просмотров: ",
"search_filters_title": "Фильтр",
"search_filters_duration_option_none": "Любой длины",
@@ -476,7 +476,7 @@
"search_message_no_results": "Ничего не найдено.",
"search_message_use_another_instance": " Дополнительно вы можете <a href=\"`x`\">поискать на других зеркалах</a>.",
"search_filters_features_option_vr180": "VR180",
- "search_message_change_filters_or_query": "Попробуйте расширить поисковый запрос или изменить фильтры.",
+ "search_message_change_filters_or_query": "Попробуйте расширить поисковый запрос и/или изменить фильтры.",
"search_filters_duration_option_medium": "Средние (4 - 20 минут)",
"search_filters_apply_button": "Применить фильтры",
"Popular enabled: ": "Популярное включено: ",
@@ -503,5 +503,6 @@
"channel_tab_podcasts_label": "Подкасты",
"generic_channels_count_0": "{{count}} канал",
"generic_channels_count_1": "{{count}} канала",
- "generic_channels_count_2": "{{count}} каналов"
+ "generic_channels_count_2": "{{count}} каналов",
+ "Import YouTube watch history (.json)": "Импортировать историю просмотра из YouTube (.json)"
}
diff --git a/locales/sl.json b/locales/sl.json
index 9a912f2d..3803d09c 100644
--- a/locales/sl.json
+++ b/locales/sl.json
@@ -520,5 +520,6 @@
"generic_channels_count_0": "{{count}} kanal",
"generic_channels_count_1": "{{count}} kanala",
"generic_channels_count_2": "{{count}} kanali",
- "generic_channels_count_3": "{{count}} kanalov"
+ "generic_channels_count_3": "{{count}} kanalov",
+ "Import YouTube watch history (.json)": "Uvozi zgodovino gledanja YouTube (.json)"
}
diff --git a/locales/sq.json b/locales/sq.json
index 41d4161c..363a70b0 100644
--- a/locales/sq.json
+++ b/locales/sq.json
@@ -79,7 +79,7 @@
"invidious": "Invidious",
"preferences_captions_label": "Titra parazgjedhje: ",
"preferences_extend_desc_label": "Zgjero automatikisht përshkrimin e videos: ",
- "preferences_player_style_label": "Silt lojtësi: ",
+ "preferences_player_style_label": "Stil lojtësi: ",
"Dark mode: ": "Mënyra e errët: ",
"preferences_dark_mode_label": "Temë: ",
"dark": "e errët",
@@ -477,5 +477,12 @@
"channel_tab_releases_label": "Hedhje në qarkullim",
"Song: ": "Pjesë: ",
"Import YouTube playlist (.csv)": "Importoni luajlistë YouTube (.csv)",
- "Standard YouTube license": "Licencë YouTube standarde"
+ "Standard YouTube license": "Licencë YouTube standarde",
+ "published - reverse": "publikuar më - së prapthi",
+ "channel_tab_podcasts_label": "Podcast-e",
+ "channel name - reverse": "emër kanali - së prapthi",
+ "Import YouTube watch history (.json)": "Importo historik parjesh YouTube (.json)",
+ "preferences_local_label": "Video përmes ndërmjetësi: ",
+ "Fallback captions: ": "Titra nga halli: ",
+ "Erroneous challenge": "Zgjidhje e gabuar"
}
diff --git a/locales/sr.json b/locales/sr.json
index f0e5518d..b4a98da6 100644
--- a/locales/sr.json
+++ b/locales/sr.json
@@ -503,5 +503,6 @@
"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"
+ "generic_views_count_2": "{{count}} pregleda",
+ "Import YouTube watch history (.json)": "Uvezi YouTube istoriju gledanja (.json)"
}
diff --git a/locales/sr_Cyrl.json b/locales/sr_Cyrl.json
index bf439b28..52ac4116 100644
--- a/locales/sr_Cyrl.json
+++ b/locales/sr_Cyrl.json
@@ -503,5 +503,7 @@
"crash_page_you_found_a_bug": "Изгледа да сте пронашли грешку у Invidious-у!",
"generic_views_count_0": "{{count}} преглед",
"generic_views_count_1": "{{count}} прегледа",
- "generic_views_count_2": "{{count}} прегледа"
+ "generic_views_count_2": "{{count}} прегледа",
+ "Import YouTube watch history (.json)": "Увези YouTube историју гледањa (.json)",
+ "toggle_theme": "Укључи тему"
}
diff --git a/locales/sv-SE.json b/locales/sv-SE.json
index a319fffd..db3486df 100644
--- a/locales/sv-SE.json
+++ b/locales/sv-SE.json
@@ -20,15 +20,15 @@
"No": "Nej",
"Import and Export Data": "Importera och exportera data",
"Import": "Importera",
- "Import Invidious data": "Importera Invidious-data",
- "Import YouTube subscriptions": "Importera YouTube-prenumerationer",
+ "Import Invidious data": "Importera Invidious JSON data",
+ "Import YouTube subscriptions": "Importera YouTube/OPML prenumerationer",
"Import FreeTube subscriptions (.db)": "Importera FreeTube-prenumerationer (.db)",
"Import NewPipe subscriptions (.json)": "Importera NewPipe-prenumerationer (.json)",
"Import NewPipe data (.zip)": "Importera NewPipe-data (.zip)",
"Export": "Exportera",
"Export subscriptions as OPML": "Exportera prenumerationer som OPML",
"Export subscriptions as OPML (for NewPipe & FreeTube)": "Exportera prenumerationer som OPML (för NewPipe och FreeTube)",
- "Export data as JSON": "Exportera data som JSON",
+ "Export data as JSON": "Exportera Invidious data som JSON",
"Delete account?": "Radera konto?",
"History": "Historik",
"An alternative front-end to YouTube": "Ett alternativt gränssnitt till YouTube",
@@ -63,7 +63,7 @@
"preferences_related_videos_label": "Visa relaterade videor? ",
"preferences_annotations_label": "Visa länkar-i-videon som förval? ",
"preferences_extend_desc_label": "Förläng videobeskrivning automatiskt: ",
- "preferences_vr_mode_label": "Interaktiva 360-gradervideos: ",
+ "preferences_vr_mode_label": "Interaktiva 360-gradervideos (kräver WebGL): ",
"preferences_category_visual": "Visuella inställningar",
"preferences_player_style_label": "Spelarstil: ",
"Dark mode: ": "Mörkt läge: ",
@@ -152,7 +152,7 @@
"View YouTube comments": "Visa YouTube-kommentarer",
"View more comments on Reddit": "Visa flera kommentarer på Reddit",
"View `x` comments": {
- "([^.,0-9]|^)1([^.,0-9]|$)": "Visa `x` kommentarer",
+ "([^.,0-9]|^)1([^.,0-9]|$)": "Visa `x` kommentar",
"": "Visa `x` kommentarer"
},
"View Reddit comments": "Visa Reddit-kommentarer",
@@ -167,7 +167,7 @@
"Wrong username or password": "Ogiltigt användarnamn eller lösenord",
"Password cannot be empty": "Lösenordet kan inte vara tomt",
"Password cannot be longer than 55 characters": "Lösenordet kan inte vara längre än 55 tecken",
- "Please log in": "Logga in",
+ "Please log in": "Snälla logga in",
"Invidious Private Feed for `x`": "Ogiltig privat flöde för `x`",
"channel:`x`": "kanal `x`",
"Deleted or invalid channel": "Raderad eller ogiltig kanal",
@@ -311,8 +311,8 @@
"%A %B %-d, %Y": "%A %B %-d, %Y",
"(edited)": "(redigerad)",
"YouTube comment permalink": "Permanent YouTube-länk till innehållet",
- "permalink": "permalänk",
- "`x` marked it with a ❤": "`x` lämnade ett ❤",
+ "permalink": "permanent länk",
+ "`x` marked it with a ❤": "`x` markerade det med ett ❤",
"Audio mode": "Ljudläge",
"Video mode": "Videoläge",
"channel_tab_videos_label": "Videor",
@@ -320,30 +320,30 @@
"channel_tab_community_label": "Gemenskap",
"search_filters_sort_option_relevance": "Relevans",
"search_filters_sort_option_rating": "Rankning",
- "search_filters_sort_option_date": "Datum",
+ "search_filters_sort_option_date": "Uppladdnings Datum",
"search_filters_sort_option_views": "Visningar",
"search_filters_type_label": "Typ",
"search_filters_duration_label": "Varaktighet",
"search_filters_features_label": "Funktioner",
"search_filters_sort_label": "Sortera efter",
- "search_filters_date_option_hour": "timme",
- "search_filters_date_option_today": "idag",
- "search_filters_date_option_week": "vecka",
- "search_filters_date_option_month": "månad",
- "search_filters_date_option_year": "år",
- "search_filters_type_option_video": "video",
- "search_filters_type_option_channel": "kanal",
- "search_filters_type_option_playlist": "spellista",
- "search_filters_type_option_movie": "film",
- "search_filters_type_option_show": "tv-serie",
- "search_filters_features_option_hd": "hd",
- "search_filters_features_option_subtitles": "undertexter",
- "search_filters_features_option_c_commons": "creative_commons",
- "search_filters_features_option_three_d": "3d",
- "search_filters_features_option_live": "live",
- "search_filters_features_option_four_k": "4k",
- "search_filters_features_option_location": "plats",
- "search_filters_features_option_hdr": "hdr",
+ "search_filters_date_option_hour": "Senaste Timmen",
+ "search_filters_date_option_today": "Idag",
+ "search_filters_date_option_week": "Denna vecka",
+ "search_filters_date_option_month": "Denna månad",
+ "search_filters_date_option_year": "Detta år",
+ "search_filters_type_option_video": "Video",
+ "search_filters_type_option_channel": "Kanal",
+ "search_filters_type_option_playlist": "Spellista",
+ "search_filters_type_option_movie": "Film",
+ "search_filters_type_option_show": "Serie",
+ "search_filters_features_option_hd": "HD",
+ "search_filters_features_option_subtitles": "Undertexter/CC",
+ "search_filters_features_option_c_commons": "Creative Commons",
+ "search_filters_features_option_three_d": "3D",
+ "search_filters_features_option_live": "Live",
+ "search_filters_features_option_four_k": "4K",
+ "search_filters_features_option_location": "Plats",
+ "search_filters_features_option_hdr": "HDR",
"Current version: ": "Nuvarande version: ",
"next_steps_error_message_refresh": "Uppdatera",
"next_steps_error_message_go_to_youtube": "Gå till Youtube",
@@ -352,5 +352,141 @@
"search_filters_duration_option_long": "Lång (> 20 minuter)",
"footer_documentation": "Dokumentation",
"search_filters_duration_option_short": "Kort (< 4 minuter)",
- "search_filters_title": "Filter"
+ "search_filters_title": "Filter",
+ "Korean (auto-generated)": "Koreanska (auto-genererad)",
+ "search_filters_features_option_three_sixty": "360°",
+ "preferences_quality_dash_option_worst": "Sämst",
+ "channel_tab_podcasts_label": "Podcaster",
+ "preferences_save_player_pos_label": "Spara uppspelningsposition: ",
+ "Spanish (Mexico)": "Spanska (Mexiko)",
+ "preferences_region_label": "Innehållsland: ",
+ "generic_subscriptions_count": "{{count}} prenumeration",
+ "generic_subscriptions_count_plural": "{{count}} prenumerationer",
+ "search_filters_apply_button": "Använd valda filter",
+ "Download is disabled": "Nedladdning är inaktiverad",
+ "comments_points_count": "{{count}} poäng",
+ "comments_points_count_plural": "{{count}} poäng",
+ "preferences_quality_dash_option_2160p": "2160p",
+ "German (auto-generated)": "Tyska (auto-genererad)",
+ "Japanese (auto-generated)": "Japanska (auto-genererad)",
+ "preferences_quality_option_medium": "Medium",
+ "footer_donate_page": "Donera",
+ "search_message_change_filters_or_query": "Prova att bredda din sökfråga och/eller ändra filtren.",
+ "crash_page_before_reporting": "Innan du rapporterar en bugg, se till att du har:",
+ "preferences_quality_dash_option_best": "Bäst",
+ "Channel Sponsor": "Kanal Sponsor",
+ "generic_videos_count": "{{count}} video",
+ "generic_videos_count_plural": "{{count}} videor",
+ "videoinfo_started_streaming_x_ago": "Började sända `x` sedan",
+ "videoinfo_youTube_embed_link": "Bädda in",
+ "channel_tab_streams_label": "Livesändningar",
+ "playlist_button_add_items": "Lägg till videor",
+ "generic_count_minutes": "{{count}}minut",
+ "generic_count_minutes_plural": "{{count}}minuter",
+ "preferences_quality_dash_option_720p": "720p",
+ "preferences_watch_history_label": "Aktivera visningshistorik: ",
+ "user_saved_playlists": "`x` sparade spellistor",
+ "Spanish (Spain)": "Spanska (Spanien)",
+ "invidious": "Invidious",
+ "crash_page_refresh": "försökte <a href=\"`x`\">uppdatera sidan</a>",
+ "Chinese (Hong Kong)": "Kinesiska (Hong Kong)",
+ "Artist: ": "Artist: ",
+ "generic_count_months": "{{count}}månad",
+ "generic_count_months_plural": "{{count}}månader",
+ "search_message_use_another_instance": " Du kan också <a href=\"`x`\">söka på en annan instans</a>.",
+ "generic_subscribers_count": "{{count}} prenumerant",
+ "generic_subscribers_count_plural": "{{count}} prenumeranter",
+ "download_subtitles": "Undertexter - `x` (.vtt)",
+ "generic_button_save": "Spara",
+ "crash_page_search_issue": "sökte efter <a href=\"`x`\">befintliga problem på GitHub</a>",
+ "generic_button_cancel": "Avbryt",
+ "none": "ingen",
+ "English (United States)": "English (Förenta staterna)",
+ "subscriptions_unseen_notifs_count": "{{count}}osedd notifikation",
+ "subscriptions_unseen_notifs_count_plural": "{{count}}osedda notifikationer",
+ "Album: ": "Album: ",
+ "preferences_quality_option_dash": "DASH (adaptiv kvalitet)",
+ "preferences_quality_dash_option_1080p": "1080p",
+ "Video unavailable": "Video inte tillgänglig",
+ "tokens_count": "{{count}}nyckel",
+ "tokens_count_plural": "{{count}}nycklar",
+ "Chinese (China)": "Kinesiska (Kina)",
+ "Italian (auto-generated)": "Italienska (auto-genererad)",
+ "channel_tab_shorts_label": "Shorts",
+ "preferences_quality_dash_option_1440p": "1440p",
+ "preferences_quality_dash_option_360p": "360p",
+ "search_message_no_results": "Inga resultat hittades.",
+ "channel_tab_releases_label": "Releaser",
+ "preferences_quality_dash_option_144p": "144p",
+ "Interlingue": "Interlingue (auto-genererad)",
+ "Song: ": "Låt: ",
+ "generic_channels_count": "{{count}} kanal",
+ "generic_channels_count_plural": "{{count}} kanaler",
+ "Chinese (Taiwan)": "Kinesiska (Taiwan)",
+ "preferences_quality_dash_label": "Önskad DASH-videokvalitet: ",
+ "adminprefs_modified_source_code_url_label": "URL till modifierad källkodslager",
+ "Turkish (auto-generated)": "Turkiska (auto-genererad)",
+ "Indonesian (auto-generated)": "Indonesiska (auto-genererad)",
+ "Portuguese (auto-generated)": "Portugisiska (auto-genererad)",
+ "generic_count_years": "{{count}}år",
+ "generic_count_years_plural": "{{count}}år",
+ "videoinfo_invidious_embed_link": "Bädda in länk",
+ "Popular enabled: ": "Populär aktiverad: ",
+ "Spanish (auto-generated)": "Spanska (auto-genererad)",
+ "preferences_quality_option_small": "Liten",
+ "English (United Kingdom)": "Engelska (Storbritannien)",
+ "channel_tab_playlists_label": "Spellistor",
+ "generic_button_edit": "Redigera",
+ "generic_playlists_count": "{{count}} spellista",
+ "generic_playlists_count_plural": "{{count}} spellistor",
+ "preferences_quality_option_hd720": "HD720p",
+ "search_filters_features_option_purchased": "Köpt",
+ "search_filters_date_option_none": "Vilket datum som helst",
+ "preferences_quality_dash_option_auto": "Auto",
+ "Cantonese (Hong Kong)": "Katonesiska (Hong Kong)",
+ "crash_page_report_issue": "Om inget av ovanstående hjälpte, vänligen <a href=\"`x`\">öppna ett nytt nummer på GitHub</a> (helst på engelska) och inkludera följande text i ditt meddelande (översätt INTE den texten):",
+ "crash_page_switch_instance": "försökte <a href=\"`x`\">använda en annan instans</a>",
+ "generic_count_weeks": "{{count}}vecka",
+ "generic_count_weeks_plural": "{{count}}veckor",
+ "videoinfo_watch_on_youTube": "Titta på YouTube",
+ "Music in this video": "Musik i denna video",
+ "footer_modfied_source_code": "Modifierad källkod",
+ "generic_button_rss": "RSS",
+ "preferences_quality_dash_option_4320p": "4320p",
+ "generic_count_hours": "{{count}}timme",
+ "generic_count_hours_plural": "{{count}}timmar",
+ "French (auto-generated)": "Franska (auto-genererad)",
+ "crash_page_read_the_faq": "läs <a href=\"`x`\">Vanliga frågor (FAQ)</a>",
+ "user_created_playlists": "`x` skapade spellistor",
+ "channel_tab_channels_label": "Kanaler",
+ "search_filters_type_option_all": "Vilken typ som helst",
+ "Russian (auto-generated)": "Ryska (auto-genererad)",
+ "preferences_quality_dash_option_480p": "480p",
+ "comments_view_x_replies": "Se {{count}} svar",
+ "comments_view_x_replies_plural": "Se {{count}} svar",
+ "footer_original_source_code": "Ursprunglig källkod",
+ "Portuguese (Brazil)": "Portugisiska (Brasilien)",
+ "search_filters_features_option_vr180": "VR180",
+ "error_video_not_in_playlist": "Den begärda videon finns inte i den här spellistan. <a href=\"`x`\">Klicka här för startsidan för spellistan.</a>",
+ "Dutch (auto-generated)": "Nederländska (auto-genererad)",
+ "generic_count_days": "{{count}}dag",
+ "generic_count_days_plural": "{{count}}dagar",
+ "Vietnamese (auto-generated)": "Vietnamesiska (auto-genererad)",
+ "search_filters_duration_option_none": "Vilken varaktighet som helst",
+ "preferences_quality_dash_option_240p": "240p",
+ "Chinese": "Kinesiska",
+ "preferences_automatic_instance_redirect_label": "Automatisk instansomdirigering (återgång till redirect.invidious.io): ",
+ "generic_button_delete": "Radera",
+ "Import YouTube playlist (.csv)": "Importera YouTube spellista (.csv)",
+ "next_steps_error_message": "Därefter bör du försöka: ",
+ "Standard YouTube license": "Standard YouTube licens",
+ "Import YouTube watch history (.json)": "Importera YouTube visningshistorik (.json)",
+ "search_filters_duration_option_medium": "Medium (4 - 20 minuter)",
+ "generic_count_seconds": "{{count}}sekund",
+ "generic_count_seconds_plural": "{{count}}sekunder",
+ "search_filters_date_label": "Uppladdningsdatum",
+ "crash_page_you_found_a_bug": "Det verkar som att du har hittat en bugg i Invidious!",
+ "generic_views_count": "{{count}} visning",
+ "generic_views_count_plural": "{{count}} visningar",
+ "toggle_theme": "Växla tema"
}
diff --git a/locales/tr.json b/locales/tr.json
index 0575a4dd..d25cfd65 100644
--- a/locales/tr.json
+++ b/locales/tr.json
@@ -486,5 +486,7 @@
"playlist_button_add_items": "Video ekle",
"channel_tab_podcasts_label": "Podcast'ler",
"generic_channels_count": "{{count}} kanal",
- "generic_channels_count_plural": "{{count}} kanal"
+ "generic_channels_count_plural": "{{count}} kanal",
+ "Import YouTube watch history (.json)": "YouTube İzleme Geçmişini İçe Aktar (.json)",
+ "toggle_theme": "Temayı Değiştir"
}
diff --git a/locales/uk.json b/locales/uk.json
index c26618fe..f9640bba 100644
--- a/locales/uk.json
+++ b/locales/uk.json
@@ -503,5 +503,7 @@
"generic_button_save": "Зберегти",
"generic_channels_count_0": "{{count}} канал",
"generic_channels_count_1": "{{count}} канали",
- "generic_channels_count_2": "{{count}} каналів"
+ "generic_channels_count_2": "{{count}} каналів",
+ "Import YouTube watch history (.json)": "Імпортувати історію переглядів YouTube (.json)",
+ "toggle_theme": "Перемкнути тему"
}
diff --git a/locales/vi.json b/locales/vi.json
index 9cb87d3e..4f8dc30d 100644
--- a/locales/vi.json
+++ b/locales/vi.json
@@ -1,62 +1,62 @@
{
"generic_videos_count_0": "{{count}} video",
- "generic_subscribers_count_0": "{{count}} người theo dõi",
+ "generic_subscribers_count_0": "{{count}} người đăng ký",
"LIVE": "TRỰC TIẾP",
"Shared `x` ago": "Đã chia sẻ `x` trước",
- "Unsubscribe": "Hủy theo dõi",
- "Subscribe": "Theo dõi",
+ "Unsubscribe": "Hủy đăng ký",
+ "Subscribe": "Đăng ký",
"View channel on YouTube": "Xem kênh trên YouTube",
"View playlist on YouTube": "Xem danh sách phát trên YouTube",
- "newest": "mới nhất",
- "oldest": "lâu đời nhất",
- "popular": "phổ biến",
- "last": "Cuối cùng",
+ "newest": "Mới nhất",
+ "oldest": "Cũ nhất",
+ "popular": "Phổ biến",
+ "last": "cuối cùng",
"Next page": "Trang tiếp theo",
"Previous page": "Trang trước",
"Clear watch history?": "Xóa lịch sử xem?",
"New password": "Mật khẩu mới",
"New passwords must match": "Mật khẩu mới phải khớp",
"Authorize token?": "Cấp phép mã thông báo?",
- "Authorize token for `x`?": "Cấp phép mã thông báo cho` x`?",
- "Yes": "Đúng",
+ "Authorize token for `x`?": "Cấp phép mã thông báo cho `x`?",
+ "Yes": "Có",
"No": "Không",
"Import and Export Data": "Nhập và xuất dữ liệu",
"Import": "Nhập",
- "Import Invidious data": "Nhập dữ liệu Invidious JSON",
- "Import YouTube subscriptions": "Nhập dữ liệu thuê bao YouTube/OPML",
- "Import FreeTube subscriptions (.db)": "Nhập đăng ký FreeTube (.db)",
- "Import NewPipe subscriptions (.json)": "Nhập đăng ký NewPipe (.json)",
- "Import NewPipe data (.zip)": "Nhập dữ liệu NewPipe (.zip)",
+ "Import Invidious data": "Nhập dữ liệu Invidious dưới dạng JSON",
+ "Import YouTube subscriptions": "Nhập các kênh đã đăng ký từ YouTube/OPML",
+ "Import FreeTube subscriptions (.db)": "Nhập các kênh đã đăng ký từ FreeTube (.db)",
+ "Import NewPipe subscriptions (.json)": "Nhập các kênh đã đăng ký từ NewPipe (.json)",
+ "Import NewPipe data (.zip)": "Nhập dữ liệu từ NewPipe (.zip)",
"Export": "Xuất",
- "Export subscriptions as OPML": "Xuất đăng ký dưới dạng OPML",
- "Export subscriptions as OPML (for NewPipe & FreeTube)": "Xuất đăng ký dưới dạng OPML (cho NewPipe & FreeTube)",
+ "Export subscriptions as OPML": "Xuất các kênh đã đăng ký dưới dạng OPML",
+ "Export subscriptions as OPML (for NewPipe & FreeTube)": "Xuất các kênh đã đăng ký dưới dạng OPML (cho NewPipe & FreeTube)",
"Export data as JSON": "Xuất dữ liệu Invidious dưới dạng JSON",
"Delete account?": "Xóa tài khoản?",
"History": "Lịch sử",
- "An alternative front-end to YouTube": "Giao diện người dùng thay thế cho YouTube",
+ "An alternative front-end to YouTube": "Một front-end thay thế cho YouTube",
"JavaScript license information": "Thông tin giấy phép JavaScript",
"source": "nguồn",
"Log in": "Đăng nhập",
"Log in/register": "Đăng nhập / đăng ký",
- "User ID": "Tên người dùng",
+ "User ID": "ID người dùng",
"Password": "Mật khẩu",
- "Time (h:mm:ss):": "Thời gian (h: mm: ss):",
- "Text CAPTCHA": "Nhắn tin tới CAPTCHA",
- "Image CAPTCHA": "Hình ảnh CAPTCHA",
+ "Time (h:mm:ss):": "Thời gian (h:mm:ss):",
+ "Text CAPTCHA": "CAPTCHA dạng chữ",
+ "Image CAPTCHA": "CAPTCHA dạng ảnh",
"Sign In": "Đăng nhập",
"Register": "Đăng ký",
"E-mail": "E-mail",
"Preferences": "Sở thích",
"preferences_category_player": "Tùy chọn trình phát video",
"preferences_video_loop_label": "Luôn lặp lại: ",
- "preferences_autoplay_label": "Tự chạy: ",
+ "preferences_autoplay_label": "Tự động phát: ",
"preferences_continue_label": "Phát kế tiếp theo mặc định: ",
"preferences_continue_autoplay_label": "Tự động phát video tiếp theo: ",
"preferences_listen_label": "Nghe theo mặc định: ",
"preferences_local_label": "Video proxy: ",
"preferences_speed_label": "Tốc độ mặc định: ",
"preferences_quality_label": "Chất lượng video ưa thích: ",
- "preferences_volume_label": "Âm lượng trình phát video: ",
+ "preferences_volume_label": "Âm lượng video: ",
"preferences_comments_label": "Nhận xét mặc định: ",
"youtube": "YouTube",
"reddit": "Reddit",
@@ -64,7 +64,7 @@
"Fallback captions: ": "Phụ đề dự phòng: ",
"preferences_related_videos_label": "Hiển thị các video có liên quan: ",
"preferences_annotations_label": "Hiển thị chú thích theo mặc định: ",
- "preferences_extend_desc_label": "Tự động mở rộng mô tả video: ",
+ "preferences_extend_desc_label": "Tự động mở rộng phần mô tả của video: ",
"preferences_vr_mode_label": "Video 360 độ tương tác (yêu cầu WebGL): ",
"preferences_category_visual": "Tùy chọn hình ảnh",
"preferences_player_style_label": "Phong cách trình phát: ",
@@ -82,24 +82,24 @@
"preferences_sort_label": "Sắp xếp video theo: ",
"published": "được phát hành",
"published - reverse": "đã xuất bản - đảo ngược",
- "alphabetically": "theo thứ tự bảng chữ cái",
- "alphabetically - reverse": "theo thứ tự bảng chữ cái - đảo ngược",
- "channel name": "Tên kênh",
- "channel name - reverse": "tên kênh - đảo ngược",
+ "alphabetically": "Thứ tự (A - Z)",
+ "alphabetically - reverse": "Thứ tự (Z - A)",
+ "channel name": "Tên kênh (A - Z)",
+ "channel name - reverse": "Tên kênh (Z - A)",
"Only show latest video from channel: ": "Chỉ hiển thị video mới nhất từ kênh: ",
"Only show latest unwatched video from channel: ": "Chỉ hiển thị video chưa xem mới nhất từ kênh: ",
- "preferences_unseen_only_label": "Chỉ hiển thị chưa xem: ",
+ "preferences_unseen_only_label": "Chỉ hiển thị các video chưa từng xem: ",
"preferences_notifications_only_label": "Chỉ hiển thị thông báo (nếu có): ",
"Enable web notifications": "Bật thông báo web",
- "`x` uploaded a video": "` x` đã tải lên một video",
- "`x` is live": "` x` đang phát trực tiếp",
+ "`x` uploaded a video": "`x` đã tải lên một video",
+ "`x` is live": "`x` đang phát trực tiếp",
"preferences_category_data": "Tùy chọn dữ liệu",
"Clear watch history": "Xóa lịch sử xem",
"Import/export data": "Nhập / xuất dữ liệu",
"Change password": "Đổi mật khẩu",
"Manage subscriptions": "Quản lý các mục đăng kí",
"Manage tokens": "Quản lý mã thông báo",
- "Watch history": "Lịch sử xem",
+ "Watch history": "Xem lịch sử",
"Delete account": "Xóa tài khoản",
"preferences_category_admin": "Tùy chọn quản trị viên",
"preferences_default_home_label": "Trang chủ mặc định: ",
@@ -121,7 +121,7 @@
"View privacy policy.": "Xem chính sách bảo mật.",
"Trending": "Xu hướng",
"Public": "Công khai",
- "Unlisted": "Không hiển thị",
+ "Unlisted": "Không công khai",
"Private": "Riêng tư",
"View all playlists": "Xem tất cả danh sách phát",
"Updated `x` ago": "Đã cập nhật` x` trước",
@@ -131,24 +131,24 @@
"Title": "Tiêu đề",
"Playlist privacy": "Bảo mật danh sách phát",
"Editing playlist `x`": "Chỉnh sửa danh sách phát` x`",
- "Show more": "Cho xem nhiều hơn",
- "Show less": "Hiện ít hơn",
+ "Show more": "Hiển thị thêm",
+ "Show less": "Hiển thị ít hơn",
"Watch on YouTube": "Xem trên YouTube",
"Switch Invidious Instance": "Chuyển phiên bản Invidious",
"Hide annotations": "Ẩn chú thích",
"Show annotations": "Hiển thị chú thích",
"Genre: ": "Thể loại: ",
"License: ": "Giấy phép: ",
- "Family friendly? ": "Gia đình thân thiện? ",
+ "Family friendly? ": "Thân thiện với gia đình? ",
"Wilson score: ": "Điểm số Wilson: ",
"Engagement: ": "Hôn ước: ",
"Whitelisted regions: ": "Các vùng nằm trong danh sách trắng: ",
- "Blacklisted regions: ": "Khu vực nằm trong danh sách đen: ",
+ "Blacklisted regions: ": "Các vùng nằm trong danh sách đen: ",
"Shared `x`": "Chia sẻ` x`",
- "View Reddit comments": "Xem nhận xét trên Reddit",
- "Hide replies": "Ẩn câu trả lời",
- "Show replies": "Hiển thị câu trả lời",
- "Incorrect password": "Mật khẩu không đúng",
+ "View Reddit comments": "Xem bình luận trên Reddit",
+ "Hide replies": "Ẩn phản hồi",
+ "Show replies": "Hiển thị phản hồi",
+ "Incorrect password": "Mật khẩu không chính xác",
"Wrong answer": "Câu trả lời sai",
"Erroneous CAPTCHA": "CAPTCHA bị lỗi",
"CAPTCHA is a required field": "CAPTCHA là trường bắt buộc",
@@ -190,35 +190,35 @@
"Bulgarian": "Tiếng Bungari",
"Burmese": "Tiếng Miến Điện",
"Catalan": "Tiếng Catalan",
- "Cebuano": "Cebuano",
+ "Cebuano": "Tiếng Cebu",
"Chinese (Simplified)": "Tiếng Trung (Giản thể)",
"Chinese (Traditional)": "Tiếng Trung (Phồn thể)",
- "Corsican": "Corsican",
+ "Corsican": "Tiếng Corse",
"Croatian": "Tiếng Croatia",
"Czech": "Tiếng Séc",
- "Danish": "Người Đan Mạch",
+ "Danish": "Tiếng Đan Mạch",
"Dutch": "Tiếng Hà Lan",
"Esperanto": "Quốc tế ngữ",
"Estonian": "Tiếng Estonia",
- "Filipino": "Filipino",
+ "Filipino": "Tiếng Philippines",
"Finnish": "Tiếng Phần Lan",
- "French": "Người Pháp",
+ "French": "Tiếng Pháp",
"Galician": "Tiếng Galicia",
"Georgian": "Tiếng Georgia",
"German": "Tiếng Đức",
- "Greek": "Người Hy Lạp",
- "Gujarati": "Gujarati",
- "Haitian Creole": "Tiếng Creole của Haiti",
- "Hausa": "Hausa",
+ "Greek": "Tiếng Hy Lạp",
+ "Gujarati": "Tiếng Gujarat",
+ "Haitian Creole": "Tiếng Creole (Haiti)",
+ "Hausa": "Tiếng Hausa",
"Hawaiian": "Tiếng Hawaii",
"Hebrew": "Tiếng Do Thái",
"Hindi": "Tiếng Hindi",
- "Hmong": "Hmong",
- "Hungarian": "Người Hungary",
+ "Hmong": "Tiếng Hmong",
+ "Hungarian": "Tiếng Hungary",
"Icelandic": "Tiếng Iceland",
- "Igbo": "Igbo",
+ "Igbo": "Tiếng Igbo",
"Indonesian": "Tiếng Indonesia",
- "Irish": "Tiếng Ailen",
+ "Irish": "Tiếng Ireland",
"Italian": "Tiếng Ý",
"Japanese": "Tiếng Nhật",
"Javanese": "Tiếng Java",
@@ -237,37 +237,37 @@
"Malagasy": "Tiếng Malagasy",
"Malay": "Tiếng Mã Lai",
"Malayalam": "Tiếng Malayalam",
- "Maltese": "Cây nho",
+ "Maltese": "Tiếng Malta",
"Maori": "Tiếng Maori",
- "Marathi": "Marathi",
+ "Marathi": "Tiếng Marathi",
"Mongolian": "Tiếng Mông Cổ",
"Nepali": "Tiếng Nepal",
- "Norwegian Bokmål": "Tiếng Na Uy Bokmål",
- "Nyanja": "Nyanja",
- "Pashto": "Pashto",
+ "Norwegian Bokmål": "Tiếng Na Uy (Bokmål)",
+ "Nyanja": "Tiếng Chewa / Nyanja",
+ "Pashto": "Tiếng Pashtun",
"Persian": "Tiếng Ba Tư",
- "Polish": "Đánh bóng",
+ "Polish": "Tiếng Ba Lan",
"Portuguese": "Tiếng Bồ Đào Nha",
- "Punjabi": "Punjabi",
+ "Punjabi": "Tiếng Punjab",
"Romanian": "Tiếng Rumani",
"Russian": "Tiếng Nga",
- "Samoan": "Samoan",
- "Scottish Gaelic": "Tiếng Gaelic Scotland",
+ "Samoan": "Tiếng Samoa",
+ "Scottish Gaelic": "Tiếng Gaelic (Scotland)",
"Serbian": "Tiếng Serbia",
- "Shona": "Shona",
- "Sindhi": "Sindhi",
- "Sinhala": "Sinhala",
+ "Shona": "Tiếng Shona",
+ "Sindhi": "Tiếng Sindh",
+ "Sinhala": "Tiếng Sinhala",
"Slovak": "Tiếng Slovak",
"Slovenian": "Tiếng Slovenia",
"Somali": "Tiếng Somali",
"Southern Sotho": "Southern Sotho",
- "Spanish": "Người Tây Ban Nha",
+ "Spanish": "Tiếng Tây Ban Nha",
"Spanish (Latin America)": "Tiếng Tây Ban Nha (Mỹ Latinh)",
"Sundanese": "Tiếng Sundan",
"Swahili": "Tiếng Swahili",
"Swedish": "Tiếng Thụy Điển",
- "Tajik": "Tajik",
- "Tamil": "Tamil",
+ "Tajik": "Tiếng Tajik",
+ "Tamil": "Tiếng Tamil",
"Telugu": "Tiếng Telugu",
"Thai": "Tiếng Thái",
"Turkish": "Tiếng Thổ Nhĩ Kỳ",
@@ -275,17 +275,17 @@
"Urdu": "Tiếng Urdu",
"Uzbek": "Tiếng Uzbek",
"Vietnamese": "Tiếng Việt",
- "Welsh": "Người xứ Wales",
- "Western Frisian": "Western Frisian",
- "Xhosa": "Xhosa",
- "Yiddish": "Yiddish",
- "Yoruba": "Yoruba",
+ "Welsh": "Tiếng Wales",
+ "Western Frisian": "Tiếng Tây Frisia",
+ "Xhosa": "Tiếng Nam Phi",
+ "Yiddish": "Tiếng Yiddish",
+ "Yoruba": "Tiếng Yoruba",
"Zulu": "Tiếng Zulu",
"Fallback comments: ": "Nhận xét dự phòng: ",
"Popular": "Phổ biến",
"Search": "Tìm kiếm",
"Top": "Hàng đầu",
- "About": "Trong khoảng",
+ "About": "Giới thiệu",
"Rating: ": "Xếp hạng: ",
"preferences_locale_label": "Ngôn ngữ: ",
"View as playlist": "Xem dưới dạng danh sách phát",
@@ -295,45 +295,45 @@
"News": "Tin tức",
"Movies": "Phim",
"Download": "Tải xuống",
- "Download as: ": "Tải tệp dưới dạng: ",
+ "Download as: ": "Tải xuống dưới dạng: ",
"%A %B %-d, %Y": "% A% B% -d,% Y",
"(edited)": "(đã chỉnh sửa)",
"YouTube comment permalink": "Liên kết cố định nhận xét trên YouTube",
"permalink": "liên kết cố định",
"`x` marked it with a ❤": "` x` đã đánh dấu nó bằng một ❤",
- "Audio mode": "Chế độ âm thanh",
- "Video mode": "Chế độ quay",
+ "Audio mode": "Chế độ audio",
+ "Video mode": "Chế độ video",
"channel_tab_videos_label": "Video",
"Playlists": "Danh sách phát",
"channel_tab_community_label": "Cộng đồng",
- "search_filters_sort_option_relevance": "liên quan",
+ "search_filters_sort_option_relevance": "Liên quan",
"search_filters_sort_option_rating": "Xếp hạng",
- "search_filters_sort_option_date": "ngày",
- "search_filters_sort_option_views": "lượt xem",
- "search_filters_type_label": "content_type",
- "search_filters_duration_label": "thời lượng",
- "search_filters_features_label": "đặc trưng",
- "search_filters_sort_label": "sắp xếp",
- "search_filters_date_option_hour": "giờ",
- "search_filters_date_option_today": "hôm nay",
- "search_filters_date_option_week": "tuần",
- "search_filters_date_option_month": "tháng",
- "search_filters_date_option_year": "năm",
+ "search_filters_sort_option_date": "Ngày tải lên",
+ "search_filters_sort_option_views": "Lượt xem",
+ "search_filters_type_label": "Thể loại",
+ "search_filters_duration_label": "Thời lượng",
+ "search_filters_features_label": "Đặc điểm",
+ "search_filters_sort_label": "Sắp xếp theo",
+ "search_filters_date_option_hour": "Một giờ qua",
+ "search_filters_date_option_today": "Hôm nay",
+ "search_filters_date_option_week": "Tuần này",
+ "search_filters_date_option_month": "Tháng này",
+ "search_filters_date_option_year": "Năm này",
"search_filters_type_option_video": "video",
- "search_filters_type_option_channel": "kênh",
- "search_filters_type_option_playlist": "danh sách phát",
- "search_filters_type_option_movie": "bộ phim",
- "search_filters_type_option_show": "chỉ",
- "search_filters_features_option_hd": "hd",
- "search_filters_features_option_subtitles": "phụ đề",
- "search_filters_features_option_c_commons": "Commons sáng tạo",
- "search_filters_features_option_three_d": "3d",
- "search_filters_features_option_live": "trực tiếp",
- "search_filters_features_option_four_k": "4k",
- "search_filters_features_option_location": "vị trí",
- "search_filters_features_option_hdr": "hdr",
+ "search_filters_type_option_channel": "Kênh",
+ "search_filters_type_option_playlist": "Danh sách phát",
+ "search_filters_type_option_movie": "Phim",
+ "search_filters_type_option_show": "Hiện",
+ "search_filters_features_option_hd": "HD",
+ "search_filters_features_option_subtitles": "Phụ đề",
+ "search_filters_features_option_c_commons": "Giấy phép Creative Commons",
+ "search_filters_features_option_three_d": "3D",
+ "search_filters_features_option_live": "Trực tiếp",
+ "search_filters_features_option_four_k": "4K",
+ "search_filters_features_option_location": "Vị trí",
+ "search_filters_features_option_hdr": "HDR",
"Current version: ": "Phiên bản hiện tại: ",
- "search_filters_title": "bộ lọc",
+ "search_filters_title": "Bộ lọc",
"generic_playlists_count": "{{count}} danh sách phát",
"generic_views_count": "{{count}} lượt xem",
"View `x` comments": {
@@ -350,31 +350,31 @@
"preferences_quality_dash_label": "Chất lượng video DASH ưa thích ",
"preferences_quality_dash_option_auto": "Tự động",
"Subscriptions": "Thuê bao",
- "View YouTube comments": "Hiển thị bình luận trên YouTube",
+ "View YouTube comments": "Hiển thị bình luận từ YouTube",
"View more comments on Reddit": "Hiển thị thêm bình luận từ Reddit",
"Music in this video": "Nhạc trong video này",
"Artist: ": "Nghệ sĩ: ",
"Premieres `x`": "Phát lần đầu `x`",
"preferences_region_label": "Nội dung theo quốc gia ",
"search_message_change_filters_or_query": "Thử mở rộng nội dung tìm kiếm hoặc thay đổi bộ lọc.",
- "preferences_quality_option_small": "Nhỏ",
+ "preferences_quality_option_small": "Thấp",
"preferences_quality_dash_option_144p": "144p",
"invidious": "Invidious",
"preferences_quality_dash_option_240p": "240p",
- "Import/export": "Xuất/nhập dữ liệu",
- "preferences_quality_dash_option_4320p": "4320p",
+ "Import/export": "Nhập/Xuất",
+ "preferences_quality_dash_option_4320p": "4320p (8K)",
"preferences_quality_option_dash": "DASH (tự tối ưu chất lượng)",
"generic_subscriptions_count_0": "{{count}} người đăng kí",
- "preferences_quality_dash_option_1440p": "1440p",
+ "preferences_quality_dash_option_1440p": "1440p (2K)",
"preferences_quality_dash_option_480p": "480p",
- "preferences_quality_dash_option_2160p": "2160p",
+ "preferences_quality_dash_option_2160p": "2160p (4K)",
"search_message_no_results": "Tìm kiếm không có kết quả.",
"preferences_quality_dash_option_1080p": "1080p",
"preferences_quality_dash_option_720p": "720p",
"preferences_quality_option_medium": "Trung bình",
- "Load more": "Hiển thị thêm",
+ "Load more": "Tải thêm",
"comments_points_count_0": "{{count}} điểm",
- "Import YouTube playlist (.csv)": "Nhập danh sách phát YouTube (.csv)",
+ "Import YouTube playlist (.csv)": "Nhập các danh sách phát từ YouTube (.csv)",
"preferences_quality_dash_option_best": "Tốt nhất",
"preferences_quality_dash_option_360p": "360p",
"subscriptions_unseen_notifs_count_0": "{{count}} thông báo chưa đọc",
@@ -382,10 +382,93 @@
"search_message_use_another_instance": " Bạn cũng có thể tìm kiếm <a href=\"`x`\"> ở một phiên bản khác</a>.",
"Standard YouTube license": "Giấy phép YouTube thông thường",
"Album: ": "Album: ",
- "preferences_save_player_pos_label": "Lưu vị trí xem cuối cùng ",
+ "preferences_save_player_pos_label": "Lưu vị trí xem: ",
"Hi! Looks like you have JavaScript turned off. Click here to view comments, keep in mind they may take a bit longer to load.": "Xin chào! Có vẻ như bạn đã tắt JavaScript. Bấm vào đây để xem bình luận, lưu ý rằng thời gian tải có thể lâu hơn.",
"Chinese (China)": "Tiếng Trung (Trung Quốc)",
"generic_button_cancel": "Hủy",
"Chinese": "Tiếng Trung",
- "generic_button_delete": "Xóa"
+ "generic_button_delete": "Xóa",
+ "Korean (auto-generated)": "Tiếng Hàn (được tạo tự động)",
+ "search_filters_features_option_three_sixty": "360°",
+ "channel_tab_podcasts_label": "Podcast",
+ "Spanish (Mexico)": "Tiếng Tây Ban Nha (Mexico)",
+ "search_filters_apply_button": "Áp dụng các mục đã chọn",
+ "Download is disabled": "Tải xuống đã bị vô hiệu hóa.",
+ "next_steps_error_message_go_to_youtube": "Đi đến YouTube",
+ "German (auto-generated)": "Tiếng Đức (được tạo tự động)",
+ "Japanese (auto-generated)": "Tiếng Nhật (được tạo tự động)",
+ "footer_donate_page": "Ủng hộ",
+ "crash_page_before_reporting": "Trước khi báo cáo lỗi, hãy chắc chắn rằng bạn đã:",
+ "Channel Sponsor": "Nhà tài trợ của kênh",
+ "videoinfo_started_streaming_x_ago": "Đã bắt đầu phát sóng `x` trước",
+ "videoinfo_youTube_embed_link": "Nhúng",
+ "channel_tab_streams_label": "Phát trực tiếp",
+ "playlist_button_add_items": "Thêm video",
+ "generic_count_minutes_0": "{{count}} phút",
+ "user_saved_playlists": "`x` danh sách phát đã lưu",
+ "Spanish (Spain)": "Tiếng Tây Ban Nha (Tây Ban Nha)",
+ "crash_page_refresh": "Đã thử <a href=\"`x`\">tải lại trang</a>",
+ "Chinese (Hong Kong)": "Tiếng Trung (Hồng Kông)",
+ "generic_count_months_0": "{{count}} tháng",
+ "download_subtitles": "Phụ đề - `x` (.vtt)",
+ "generic_button_save": "Lưu",
+ "crash_page_search_issue": "Tìm <a href=\"`x`\">lỗi có sẵn trên GitHub</a>",
+ "none": "không",
+ "English (United States)": "Tiếng Anh (Mỹ)",
+ "next_steps_error_message_refresh": "Tải lại",
+ "Video unavailable": "Video không có sẵn",
+ "footer_source_code": "Mã nguồn",
+ "search_filters_duration_option_short": "Ngắn (< 4 phút)",
+ "search_filters_duration_option_long": "Dài (> 20 phút)",
+ "tokens_count_0": "{{count}} mã thông báo",
+ "Italian (auto-generated)": "Tiếng Ý (được tạo tự động)",
+ "channel_tab_shorts_label": "Shorts",
+ "channel_tab_releases_label": "Mới tải lên",
+ "`x` ago": "`x` trước",
+ "Interlingue": "Tiếng Khoa học Quốc tế",
+ "generic_channels_count_0": "{{count}} kênh",
+ "Chinese (Taiwan)": "Tiếng Trung (Đài Loan)",
+ "adminprefs_modified_source_code_url_label": "URL tới kho lưu trữ mã nguồn đã sửa đổi",
+ "Turkish (auto-generated)": "Tiếng Thổ Nhĩ Kỳ (được tạo tự động)",
+ "Indonesian (auto-generated)": "Tiếng Indonesia (được tạo tự động)",
+ "Portuguese (auto-generated)": "Tiếng Bồ Đào Nha (được tạo tự động)",
+ "generic_count_years_0": "{{count}} năm",
+ "videoinfo_invidious_embed_link": "Liên kết nhúng",
+ "Popular enabled: ": "Đã bật phổ biến: ",
+ "Spanish (auto-generated)": "Tiếng Tây Ban Nha (được tạo tự động)",
+ "English (United Kingdom)": "Tiếng Anh Anh",
+ "channel_tab_playlists_label": "Danh sách phát",
+ "generic_button_edit": "Sửa",
+ "search_filters_features_option_purchased": "Đã mua",
+ "search_filters_date_option_none": "Mọi thời điểm",
+ "Cantonese (Hong Kong)": "Tiếng Quảng Châu (Hồng Kông)",
+ "crash_page_report_issue": "Nếu các điều trên không giúp được, xin hãy <a href=\"`x`\">tạo vấn đề mới trên GitHub</a> (ưu tiên tiếng Anh) và đính kèm đoạn chữ sau trong nội dung (giữ nguyên KHÔNG dịch):",
+ "crash_page_switch_instance": "Đã thử <a href=\"`x`\">dùng một phiên bản khác</a>",
+ "generic_count_weeks_0": "{{count}} tuần",
+ "videoinfo_watch_on_youTube": "Xem trên YouTube",
+ "footer_modfied_source_code": "Mã nguồn đã chỉnh sửa",
+ "generic_button_rss": "RSS",
+ "generic_count_hours_0": "{{count}} giờ",
+ "French (auto-generated)": "Tiếng Pháp (được tạo tự động)",
+ "crash_page_read_the_faq": "Đọc <a href=\"`x`\">Hỏi đáp thường gặp (FAQ)</a>",
+ "user_created_playlists": "`x` danh sách phát đã tạo",
+ "channel_tab_channels_label": "Kênh",
+ "search_filters_type_option_all": "Mọi thể loại",
+ "Russian (auto-generated)": "Tiếng Nga (được tạo tự động)",
+ "comments_view_x_replies_0": "Xem {{count}} lượt trả lời",
+ "footer_original_source_code": "Mã nguồn gốc",
+ "Portuguese (Brazil)": "Tiếng Bồ Đào Nha (Brazil)",
+ "search_filters_features_option_vr180": "VR180",
+ "error_video_not_in_playlist": "Video không tồn tại trong danh sách phát. <a href=\"`x`\">Bấm để trở về trang chủ của danh sách phát.</a>",
+ "Dutch (auto-generated)": "Tiếng Hà Lan (được tạo tự động)",
+ "generic_count_days_0": "{{count}} ngày",
+ "Vietnamese (auto-generated)": "Tiếng Việt (được tạo tự động)",
+ "search_filters_duration_option_none": "Mọi thời lượng",
+ "footer_documentation": "Tài liệu",
+ "next_steps_error_message": "Bạn có thể thử: ",
+ "Import YouTube watch history (.json)": "Nhập lịch sử xem từ YouTube (.json)",
+ "search_filters_duration_option_medium": "Trung bình (4 - 20 phút)",
+ "generic_count_seconds_0": "{{count}} giây",
+ "search_filters_date_label": "Ngày tải lên",
+ "crash_page_you_found_a_bug": "Có vẻ như bạn đã tìm ra lỗi trong Indivious!"
}
diff --git a/locales/zh-CN.json b/locales/zh-CN.json
index db86a9bf..faa67e6c 100644
--- a/locales/zh-CN.json
+++ b/locales/zh-CN.json
@@ -470,5 +470,6 @@
"generic_button_save": "保存",
"generic_button_rss": "RSS",
"channel_tab_releases_label": "公告",
- "generic_channels_count_0": "{{count}} 个频道"
+ "generic_channels_count_0": "{{count}} 个频道",
+ "toggle_theme": "切换主题"
}
diff --git a/locales/zh-TW.json b/locales/zh-TW.json
index 565f1d88..1520c269 100644
--- a/locales/zh-TW.json
+++ b/locales/zh-TW.json
@@ -470,5 +470,6 @@
"playlist_button_add_items": "新增影片",
"channel_tab_podcasts_label": "Podcast",
"channel_tab_releases_label": "發布",
- "generic_channels_count_0": "{{count}} 個頻道"
+ "generic_channels_count_0": "{{count}} 個頻道",
+ "toggle_theme": "切換佈景主題"
}
diff --git a/spec/i18next_plurals_spec.cr b/spec/i18next_plurals_spec.cr
index dab97710..dcd0f5ec 100644
--- a/spec/i18next_plurals_spec.cr
+++ b/spec/i18next_plurals_spec.cr
@@ -17,7 +17,7 @@ FORM_TESTS = {
"cy" => I18next::Plurals::PluralForms::Special_Welsh,
"fr" => I18next::Plurals::PluralForms::Special_French_Portuguese,
"en" => I18next::Plurals::PluralForms::Single_not_one,
- "es" => I18next::Plurals::PluralForms::Single_not_one,
+ "es" => I18next::Plurals::PluralForms::Special_Spanish_Italian,
"ga" => I18next::Plurals::PluralForms::Special_Irish,
"gd" => I18next::Plurals::PluralForms::Special_Scottish_Gaelic,
"he" => I18next::Plurals::PluralForms::Special_Hebrew,
@@ -33,7 +33,8 @@ FORM_TESTS = {
"mt" => I18next::Plurals::PluralForms::Special_Maltese,
"or" => I18next::Plurals::PluralForms::Special_Odia,
"pl" => I18next::Plurals::PluralForms::Special_Polish_Kashubian,
- "pt" => I18next::Plurals::PluralForms::Single_gt_one,
+ "pt" => I18next::Plurals::PluralForms::Special_French_Portuguese,
+ "pt-PT" => I18next::Plurals::PluralForms::Single_gt_one,
"pt-BR" => I18next::Plurals::PluralForms::Special_French_Portuguese,
"ro" => I18next::Plurals::PluralForms::Special_Romanian,
"sk" => I18next::Plurals::PluralForms::Special_Czech_Slovak,
@@ -77,10 +78,10 @@ SUFFIX_TESTS = {
{num: 10, suffix: "_plural"},
],
"es" => [
- {num: 0, suffix: "_plural"},
- {num: 1, suffix: ""},
- {num: 10, suffix: "_plural"},
- {num: 6_000_000, suffix: "_plural"},
+ {num: 0, suffix: "_2"},
+ {num: 1, suffix: "_0"},
+ {num: 10, suffix: "_2"},
+ {num: 6_000_000, suffix: "_1"},
],
"fr" => [
{num: 0, suffix: "_0"},
diff --git a/src/invidious.cr b/src/invidious.cr
index e0bd0101..c8cac80e 100644
--- a/src/invidious.cr
+++ b/src/invidious.cr
@@ -217,7 +217,6 @@ public_folder "assets"
Kemal.config.powered_by_header = false
add_handler FilteredCompressHandler.new
-add_handler APIHandler.new
add_handler AuthHandler.new
add_handler DenyFrame.new
add_context_storage_type(Array(String))
diff --git a/src/invidious/database/statistics.cr b/src/invidious/database/statistics.cr
index 1df549e2..9e4963fd 100644
--- a/src/invidious/database/statistics.cr
+++ b/src/invidious/database/statistics.cr
@@ -15,7 +15,7 @@ module Invidious::Database::Statistics
PG_DB.query_one(request, as: Int64)
end
- def count_users_active_1m : Int64
+ def count_users_active_6m : Int64
request = <<-SQL
SELECT count(*) FROM users
WHERE CURRENT_TIMESTAMP - updated < '6 months'
@@ -24,7 +24,7 @@ module Invidious::Database::Statistics
PG_DB.query_one(request, as: Int64)
end
- def count_users_active_6m : Int64
+ def count_users_active_1m : Int64
request = <<-SQL
SELECT count(*) FROM users
WHERE CURRENT_TIMESTAMP - updated < '1 month'
diff --git a/src/invidious/helpers/handlers.cr b/src/invidious/helpers/handlers.cr
index d140a858..cece289b 100644
--- a/src/invidious/helpers/handlers.cr
+++ b/src/invidious/helpers/handlers.cr
@@ -134,74 +134,6 @@ class AuthHandler < Kemal::Handler
end
end
-class APIHandler < Kemal::Handler
- {% for method in %w(GET POST PUT HEAD DELETE PATCH OPTIONS) %}
- only ["/api/v1/*"], {{method}}
- {% end %}
- exclude ["/api/v1/auth/notifications"], "GET"
- exclude ["/api/v1/auth/notifications"], "POST"
-
- def call(env)
- return call_next env unless only_match? env
-
- env.response.headers["Access-Control-Allow-Origin"] = "*"
-
- # Since /api/v1/notifications is an event-stream, we don't want
- # to wrap the response
- return call_next env if exclude_match? env
-
- # Here we swap out the socket IO so we can modify the response as needed
- output = env.response.output
- env.response.output = IO::Memory.new
-
- begin
- call_next env
-
- env.response.output.rewind
-
- if env.response.output.as(IO::Memory).size != 0 &&
- env.response.headers.includes_word?("Content-Type", "application/json")
- response = JSON.parse(env.response.output)
-
- if fields_text = env.params.query["fields"]?
- begin
- JSONFilter.filter(response, fields_text)
- rescue ex
- env.response.status_code = 400
- response = {"error" => ex.message}
- end
- end
-
- if env.params.query["pretty"]?.try &.== "1"
- response = response.to_pretty_json
- else
- response = response.to_json
- end
- else
- response = env.response.output.gets_to_end
- end
- rescue ex
- env.response.content_type = "application/json" if env.response.headers.includes_word?("Content-Type", "text/html")
- env.response.status_code = 500
-
- if env.response.headers.includes_word?("Content-Type", "application/json")
- response = {"error" => ex.message || "Unspecified error"}
-
- if env.params.query["pretty"]?.try &.== "1"
- response = response.to_pretty_json
- else
- response = response.to_json
- end
- end
- ensure
- env.response.output = output
- env.response.print response
-
- env.response.flush
- end
- end
-end
-
class DenyFrame < Kemal::Handler
exclude ["/embed/*"]
diff --git a/src/invidious/helpers/helpers.cr b/src/invidious/helpers/helpers.cr
index 6dc9860e..6add0237 100644
--- a/src/invidious/helpers/helpers.cr
+++ b/src/invidious/helpers/helpers.cr
@@ -78,15 +78,6 @@ def create_notification_stream(env, topics, connection_channel)
video.published = published
response = JSON.parse(video.to_json(locale, nil))
- if fields_text = env.params.query["fields"]?
- begin
- JSONFilter.filter(response, fields_text)
- rescue ex
- env.response.status_code = 400
- response = {"error" => ex.message}
- end
- end
-
env.response.puts "id: #{id}"
env.response.puts "data: #{response.to_json}"
env.response.puts
@@ -113,15 +104,6 @@ def create_notification_stream(env, topics, connection_channel)
Invidious::Database::ChannelVideos.select_notfications(topic, since_unix).each do |video|
response = JSON.parse(video.to_json(locale))
- if fields_text = env.params.query["fields"]?
- begin
- JSONFilter.filter(response, fields_text)
- rescue ex
- env.response.status_code = 400
- response = {"error" => ex.message}
- end
- end
-
env.response.puts "id: #{id}"
env.response.puts "data: #{response.to_json}"
env.response.puts
@@ -155,15 +137,6 @@ def create_notification_stream(env, topics, connection_channel)
video.published = Time.unix(published)
response = JSON.parse(video.to_json(locale, nil))
- if fields_text = env.params.query["fields"]?
- begin
- JSONFilter.filter(response, fields_text)
- rescue ex
- env.response.status_code = 400
- response = {"error" => ex.message}
- end
- end
-
env.response.puts "id: #{id}"
env.response.puts "data: #{response.to_json}"
env.response.puts
diff --git a/src/invidious/helpers/i18next.cr b/src/invidious/helpers/i18next.cr
index 252af6b9..9f4077e1 100644
--- a/src/invidious/helpers/i18next.cr
+++ b/src/invidious/helpers/i18next.cr
@@ -47,19 +47,19 @@ module I18next::Plurals
private PLURAL_SETS = {
PluralForms::Single_gt_one => [
- "ach", "ak", "am", "arn", "br", "fil", "gun", "ln", "mfe", "mg",
- "mi", "oc", "pt", "tg", "tl", "ti", "tr", "uz", "wa",
+ "ach", "ak", "am", "arn", "br", "fa", "fil", "gun", "ln", "mfe", "mg",
+ "mi", "oc", "pt-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",
+ "eo", "et", "eu", "fi", "fo", "fur", "fy", "gl", "gu", "ha", "hi",
"hu", "hy", "ia", "kk", "kn", "ku", "lb", "mai", "ml", "mn", "mr",
"nah", "nap", "nb", "ne", "nl", "nn", "no", "nso", "pa", "pap", "pms",
"ps", "rm", "sco", "se", "si", "so", "son", "sq", "sv", "sw",
"ta", "te", "tk", "ur", "yo",
],
PluralForms::None => [
- "ay", "bo", "cgg", "fa", "ht", "id", "ja", "jbo", "ka", "km", "ko", "ky",
+ "ay", "bo", "cgg", "ht", "id", "ja", "jbo", "ka", "km", "ko", "ky",
"lo", "ms", "sah", "su", "th", "tt", "ug", "vi", "wo", "zh",
],
PluralForms::Dual_Slavic => [
@@ -90,11 +90,13 @@ module I18next::Plurals
"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,
+ "es" => PluralForms::Special_Spanish_Italian,
+ "fr" => PluralForms::Special_French_Portuguese,
+ "hr" => PluralForms::Special_Hungarian_Serbian,
+ "it" => PluralForms::Special_Spanish_Italian,
+ "pt" => PluralForms::Special_French_Portuguese,
+ "pt" => PluralForms::Special_French_Portuguese,
+ "sr" => PluralForms::Special_Hungarian_Serbian,
}
# These are the v1 and v2 compatible suffixes.
@@ -165,7 +167,7 @@ module I18next::Plurals
def get_plural_form(locale : String) : PluralForms
# Extract the ISO 639-1 or 639-2 code from an RFC 5646 language code
- if !locale.matches?(/^pt-BR$/)
+ if !locale.matches?(/^pt-PT$/)
locale = locale.split('-')[0]
end
diff --git a/src/invidious/helpers/json_filter.cr b/src/invidious/helpers/json_filter.cr
deleted file mode 100644
index 3f4080ba..00000000
--- a/src/invidious/helpers/json_filter.cr
+++ /dev/null
@@ -1,248 +0,0 @@
-module JSONFilter
- alias BracketIndex = Hash(Int64, Int64)
-
- alias GroupedFieldsValue = String | Array(GroupedFieldsValue)
- alias GroupedFieldsList = Array(GroupedFieldsValue)
-
- class FieldsParser
- class ParseError < Exception
- end
-
- # Returns the `Regex` pattern used to match nest groups
- def self.nest_group_pattern : Regex
- # uses a '.' character to match json keys as they are allowed
- # to contain any unicode codepoint
- /(?:|,)(?<groupname>[^,\n]*?)\(/
- end
-
- # Returns the `Regex` pattern used to check if there are any empty nest groups
- def self.unnamed_nest_group_pattern : Regex
- /^\(|\(\(|\/\(/
- end
-
- def self.parse_fields(fields_text : String, &) : Nil
- if fields_text.empty?
- raise FieldsParser::ParseError.new "Fields is empty"
- end
-
- opening_bracket_count = fields_text.count('(')
- closing_bracket_count = fields_text.count(')')
-
- if opening_bracket_count != closing_bracket_count
- bracket_type = opening_bracket_count > closing_bracket_count ? "opening" : "closing"
- raise FieldsParser::ParseError.new "There are too many #{bracket_type} brackets (#{opening_bracket_count}:#{closing_bracket_count})"
- elsif match_result = unnamed_nest_group_pattern.match(fields_text)
- raise FieldsParser::ParseError.new "Unnamed nest group at position #{match_result.begin}"
- end
-
- # first, handle top-level single nested properties: items/id, playlistItems/snippet, etc
- parse_single_nests(fields_text) { |nest_list| yield nest_list }
-
- # next, handle nest groups: items(id, etag, etc)
- parse_nest_groups(fields_text) { |nest_list| yield nest_list }
- end
-
- def self.parse_single_nests(fields_text : String, &) : Nil
- single_nests = remove_nest_groups(fields_text)
-
- if !single_nests.empty?
- property_nests = single_nests.split(',')
-
- property_nests.each do |nest|
- nest_list = nest.split('/')
- if nest_list.includes? ""
- raise FieldsParser::ParseError.new "Empty key in nest list: #{nest_list}"
- end
- yield nest_list
- end
- # else
- # raise FieldsParser::ParseError.new "Empty key in nest list 22: #{fields_text} | #{single_nests}"
- end
- end
-
- def self.parse_nest_groups(fields_text : String, &) : Nil
- nest_stack = [] of NamedTuple(group_name: String, closing_bracket_index: Int64)
- bracket_pairs = get_bracket_pairs(fields_text, true)
-
- text_index = 0
- regex_index = 0
-
- while regex_result = self.nest_group_pattern.match(fields_text, regex_index)
- raw_match = regex_result[0]
- group_name = regex_result["groupname"]
-
- text_index = regex_result.begin
- regex_index = regex_result.end
-
- if text_index.nil? || regex_index.nil?
- raise FieldsParser::ParseError.new "Received invalid index while parsing nest groups: text_index: #{text_index} | regex_index: #{regex_index}"
- end
-
- offset = raw_match.starts_with?(',') ? 1 : 0
-
- opening_bracket_index = (text_index + group_name.size) + offset
- closing_bracket_index = bracket_pairs[opening_bracket_index]
- content_start = opening_bracket_index + 1
-
- content = fields_text[content_start...closing_bracket_index]
-
- if content.empty?
- raise FieldsParser::ParseError.new "Empty nest group at position #{content_start}"
- else
- content = remove_nest_groups(content)
- end
-
- while nest_stack.size > 0 && closing_bracket_index > nest_stack[nest_stack.size - 1][:closing_bracket_index]
- if nest_stack.size
- nest_stack.pop
- end
- end
-
- group_name.split('/').each do |name|
- nest_stack.push({
- group_name: name,
- closing_bracket_index: closing_bracket_index,
- })
- end
-
- if !content.empty?
- properties = content.split(',')
-
- properties.each do |prop|
- nest_list = nest_stack.map { |nest_prop| nest_prop[:group_name] }
-
- if !prop.empty?
- if prop.includes?('/')
- parse_single_nests(prop) { |list| nest_list += list }
- else
- nest_list.push prop
- end
- else
- raise FieldsParser::ParseError.new "Empty key in nest list: #{nest_list << prop}"
- end
-
- yield nest_list
- end
- end
- end
- end
-
- def self.remove_nest_groups(text : String) : String
- content_bracket_pairs = get_bracket_pairs(text, false)
-
- content_bracket_pairs.each_key.to_a.reverse.each do |opening_bracket|
- closing_bracket = content_bracket_pairs[opening_bracket]
- last_comma = text.rindex(',', opening_bracket) || 0
-
- text = text[0...last_comma] + text[closing_bracket + 1...text.size]
- end
-
- return text.starts_with?(',') ? text[1...text.size] : text
- end
-
- def self.get_bracket_pairs(text : String, recursive = true) : BracketIndex
- istart = [] of Int64
- bracket_index = BracketIndex.new
-
- text.each_char_with_index do |char, index|
- if char == '('
- istart.push(index.to_i64)
- end
-
- if char == ')'
- begin
- opening = istart.pop
- if recursive || (!recursive && istart.size == 0)
- bracket_index[opening] = index.to_i64
- end
- rescue
- raise FieldsParser::ParseError.new "No matching opening parenthesis at: #{index}"
- end
- end
- end
-
- if istart.size != 0
- idx = istart.pop
- raise FieldsParser::ParseError.new "No matching closing parenthesis at: #{idx}"
- end
-
- return bracket_index
- end
- end
-
- class FieldsGrouper
- alias SkeletonValue = Hash(String, SkeletonValue)
-
- def self.create_json_skeleton(fields_text : String) : SkeletonValue
- root_hash = {} of String => SkeletonValue
-
- FieldsParser.parse_fields(fields_text) do |nest_list|
- current_item = root_hash
- nest_list.each do |key|
- if current_item[key]?
- current_item = current_item[key]
- else
- current_item[key] = {} of String => SkeletonValue
- current_item = current_item[key]
- end
- end
- end
- root_hash
- end
-
- def self.create_grouped_fields_list(json_skeleton : SkeletonValue) : GroupedFieldsList
- grouped_fields_list = GroupedFieldsList.new
- json_skeleton.each do |key, value|
- grouped_fields_list.push key
-
- nested_keys = create_grouped_fields_list(value)
- grouped_fields_list.push nested_keys unless nested_keys.empty?
- end
- return grouped_fields_list
- end
- end
-
- class FilterError < Exception
- end
-
- def self.filter(item : JSON::Any, fields_text : String, in_place : Bool = true)
- skeleton = FieldsGrouper.create_json_skeleton(fields_text)
- grouped_fields_list = FieldsGrouper.create_grouped_fields_list(skeleton)
- filter(item, grouped_fields_list, in_place)
- end
-
- def self.filter(item : JSON::Any, grouped_fields_list : GroupedFieldsList, in_place : Bool = true) : JSON::Any
- item = item.clone unless in_place
-
- if !item.as_h? && !item.as_a?
- raise FilterError.new "Can't filter '#{item}' by #{grouped_fields_list}"
- end
-
- top_level_keys = Array(String).new
- grouped_fields_list.each do |value|
- if value.is_a? String
- top_level_keys.push value
- elsif value.is_a? Array
- if !top_level_keys.empty?
- key_to_filter = top_level_keys.last
-
- if item.as_h?
- filter(item[key_to_filter], value, in_place: true)
- elsif item.as_a?
- item.as_a.each { |arr_item| filter(arr_item[key_to_filter], value, in_place: true) }
- end
- else
- raise FilterError.new "Tried to filter while top level keys list is empty"
- end
- end
- end
-
- if item.as_h?
- item.as_h.select! top_level_keys
- elsif item.as_a?
- item.as_a.map { |value| filter(value, top_level_keys, in_place: true) }
- end
-
- item
- end
-end
diff --git a/src/invidious/helpers/utils.cr b/src/invidious/helpers/utils.cr
index a006d602..e438e3b9 100644
--- a/src/invidious/helpers/utils.cr
+++ b/src/invidious/helpers/utils.cr
@@ -262,7 +262,7 @@ def get_referer(env, fallback = "/", unroll = true)
end
referer = referer.request_target
- referer = "/" + referer.gsub(/[^\/?@&%=\-_.:,0-9a-zA-Z]/, "").lstrip("/\\")
+ referer = "/" + referer.gsub(/[^\/?@&%=\-_.:,*0-9a-zA-Z]/, "").lstrip("/\\")
if referer == env.request.path
referer = fallback
diff --git a/src/invidious/jobs/statistics_refresh_job.cr b/src/invidious/jobs/statistics_refresh_job.cr
index 72d1ce88..66c91ad5 100644
--- a/src/invidious/jobs/statistics_refresh_job.cr
+++ b/src/invidious/jobs/statistics_refresh_job.cr
@@ -56,8 +56,8 @@ class Invidious::Jobs::StatisticsRefreshJob < Invidious::Jobs::BaseJob
users = STATISTICS.dig("usage", "users").as(Hash(String, Int64))
users["total"] = Invidious::Database::Statistics.count_users_total
- users["activeHalfyear"] = Invidious::Database::Statistics.count_users_active_1m
- users["activeMonth"] = Invidious::Database::Statistics.count_users_active_6m
+ users["activeHalfyear"] = Invidious::Database::Statistics.count_users_active_6m
+ users["activeMonth"] = Invidious::Database::Statistics.count_users_active_1m
STATISTICS["metadata"] = {
"updatedAt" => Time.utc.to_unix,
diff --git a/src/invidious/routes/api/v1/misc.cr b/src/invidious/routes/api/v1/misc.cr
index b42ecd1a..12942906 100644
--- a/src/invidious/routes/api/v1/misc.cr
+++ b/src/invidious/routes/api/v1/misc.cr
@@ -191,6 +191,8 @@ module Invidious::Routes::API::V1::Misc
json.object do
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 "playlistId", sub_endpoint["playlistId"].as_s if sub_endpoint["playlistId"]?
+ json.field "startTimeSeconds", sub_endpoint["startTimeSeconds"].as_i if sub_endpoint["startTimeSeconds"]?
json.field "params", params.try &.as_s
json.field "pageType", pageType
end
diff --git a/src/invidious/routes/api/v1/search.cr b/src/invidious/routes/api/v1/search.cr
index 9fb283c2..2922b060 100644
--- a/src/invidious/routes/api/v1/search.cr
+++ b/src/invidious/routes/api/v1/search.cr
@@ -32,11 +32,14 @@ module Invidious::Routes::API::V1::Search
begin
client = HTTP::Client.new("suggestqueries-clients6.youtube.com")
- url = "/complete/search?client=youtube&hl=en&gl=#{region}&q=#{URI.encode_www_form(query)}&xssi=t&gs_ri=youtube&ds=yt"
+ client.before_request { |r| add_yt_headers(r) }
+
+ url = "/complete/search?client=youtube&hl=en&gl=#{region}&q=#{URI.encode_www_form(query)}&gs_ri=youtube&ds=yt"
response = client.get(url).body
+ client.close
- body = JSON.parse(response[5..-1]).as_a
+ body = JSON.parse(response[19..-2]).as_a
suggestions = body[1].as_a[0..-2]
JSON.build do |json|
diff --git a/src/invidious/routes/api/v1/videos.cr b/src/invidious/routes/api/v1/videos.cr
index 1017ac9d..9281f4dd 100644
--- a/src/invidious/routes/api/v1/videos.cr
+++ b/src/invidious/routes/api/v1/videos.cr
@@ -363,4 +363,47 @@ module Invidious::Routes::API::V1::Videos
end
end
end
+
+ def self.clips(env)
+ locale = env.get("preferences").as(Preferences).locale
+
+ env.response.content_type = "application/json"
+
+ clip_id = env.params.url["id"]
+ region = env.params.query["region"]?
+ proxy = {"1", "true"}.any? &.== env.params.query["local"]?
+
+ response = YoutubeAPI.resolve_url("https://www.youtube.com/clip/#{clip_id}")
+ return error_json(400, "Invalid clip ID") if response["error"]?
+
+ video_id = response.dig?("endpoint", "watchEndpoint", "videoId").try &.as_s
+ return error_json(400, "Invalid clip ID") if video_id.nil?
+
+ start_time = nil
+ end_time = nil
+ clip_title = nil
+
+ if params = response.dig?("endpoint", "watchEndpoint", "params").try &.as_s
+ start_time, end_time, clip_title = parse_clip_parameters(params)
+ end
+
+ begin
+ video = get_video(video_id, region: region)
+ rescue ex : NotFoundException
+ return error_json(404, ex)
+ rescue ex
+ return error_json(500, ex)
+ end
+
+ return JSON.build do |json|
+ json.object do
+ json.field "startTime", start_time
+ json.field "endTime", end_time
+ json.field "clipTitle", clip_title
+ json.field "video" do
+ Invidious::JSONify::APIv1.video(video, json, locale: locale, proxy: proxy)
+ end
+ end
+ end
+ end
end
diff --git a/src/invidious/routes/feeds.cr b/src/invidious/routes/feeds.cr
index 40bca008..e20a7139 100644
--- a/src/invidious/routes/feeds.cr
+++ b/src/invidious/routes/feeds.cr
@@ -407,14 +407,23 @@ module Invidious::Routes::Feeds
end
spawn do
- rss = XML.parse_html(body)
- rss.xpath_nodes("//feed/entry").each do |entry|
- id = entry.xpath_node("videoid").not_nil!.content
- author = entry.xpath_node("author/name").not_nil!.content
- published = Time.parse_rfc3339(entry.xpath_node("published").not_nil!.content)
- updated = Time.parse_rfc3339(entry.xpath_node("updated").not_nil!.content)
-
- video = get_video(id, force_refresh: true)
+ # TODO: unify this with the other almost identical looking parts in this and channels.cr somehow?
+ namespaces = {
+ "yt" => "http://www.youtube.com/xml/schemas/2015",
+ "default" => "http://www.w3.org/2005/Atom",
+ }
+ rss = XML.parse(body)
+ rss.xpath_nodes("//default:feed/default:entry", namespaces).each do |entry|
+ id = entry.xpath_node("yt:videoId", namespaces).not_nil!.content
+ author = entry.xpath_node("default:author/default:name", namespaces).not_nil!.content
+ published = Time.parse_rfc3339(entry.xpath_node("default:published", namespaces).not_nil!.content)
+ updated = Time.parse_rfc3339(entry.xpath_node("default:updated", namespaces).not_nil!.content)
+
+ begin
+ video = get_video(id, force_refresh: true)
+ rescue
+ next # skip this video since it raised an exception (e.g. it is a scheduled live event)
+ end
if CONFIG.enable_user_notifications
# Deliver notifications to `/api/v1/auth/notifications`
diff --git a/src/invidious/routes/watch.cr b/src/invidious/routes/watch.cr
index 3d935f0a..aabe8dfc 100644
--- a/src/invidious/routes/watch.cr
+++ b/src/invidious/routes/watch.cr
@@ -275,6 +275,12 @@ module Invidious::Routes::Watch
return error_template(400, "Invalid clip ID") if response["error"]?
if video_id = response.dig?("endpoint", "watchEndpoint", "videoId")
+ if params = response.dig?("endpoint", "watchEndpoint", "params").try &.as_s
+ start_time, end_time, _ = parse_clip_parameters(params)
+ env.params.query["start"] = start_time.to_s if start_time != nil
+ env.params.query["end"] = end_time.to_s if end_time != nil
+ end
+
return env.redirect "/watch?v=#{video_id}&#{env.params.query}"
else
return error_template(404, "The requested clip doesn't exist")
diff --git a/src/invidious/routing.cr b/src/invidious/routing.cr
index d6bd991c..ba05da19 100644
--- a/src/invidious/routing.cr
+++ b/src/invidious/routing.cr
@@ -235,6 +235,7 @@ module Invidious::Routing
get "/api/v1/captions/:id", {{namespace}}::Videos, :captions
get "/api/v1/annotations/:id", {{namespace}}::Videos, :annotations
get "/api/v1/comments/:id", {{namespace}}::Videos, :comments
+ get "/api/v1/clips/:id", {{namespace}}::Videos, :clips
# Feeds
get "/api/v1/trending", {{namespace}}::Feeds, :trending
diff --git a/src/invidious/videos/clip.cr b/src/invidious/videos/clip.cr
new file mode 100644
index 00000000..29c57182
--- /dev/null
+++ b/src/invidious/videos/clip.cr
@@ -0,0 +1,22 @@
+require "json"
+
+# returns start_time, end_time and clip_title
+def parse_clip_parameters(params) : {Float64?, Float64?, String?}
+ decoded_protobuf = params.try { |i| URI.decode_www_form(i) }
+ .try { |i| Base64.decode(i) }
+ .try { |i| IO::Memory.new(i) }
+ .try { |i| Protodec::Any.parse(i) }
+
+ start_time = decoded_protobuf
+ .try(&.["50:0:embedded"]["2:1:varint"].as_i64)
+ .try { |i| i/1000 }
+
+ end_time = decoded_protobuf
+ .try(&.["50:0:embedded"]["3:2:varint"].as_i64)
+ .try { |i| i/1000 }
+
+ clip_title = decoded_protobuf
+ .try(&.["50:0:embedded"]["4:3:string"].as_s)
+
+ return start_time, end_time, clip_title
+end
diff --git a/src/invidious/views/components/item.ecr b/src/invidious/views/components/item.ecr
index 031b46da..6d227cfc 100644
--- a/src/invidious/views/components/item.ecr
+++ b/src/invidious/views/components/item.ecr
@@ -82,11 +82,19 @@
</div>
<div class="video-card-row flexible">
- <div class="flex-left"><a href="/channel/<%= item.ucid %>">
- <p class="channel-name" dir="auto"><%= HTML.escape(item.author) %>
- <%- if author_verified %>&nbsp;<i class="icon ion ion-md-checkmark-circle"></i><% end -%>
- </p>
- </a></div>
+ <div class="flex-left">
+ <% if !item.ucid.to_s.empty? %>
+ <a href="/channel/<%= item.ucid %>">
+ <p class="channel-name" dir="auto"><%= HTML.escape(item.author) %>
+ <%- if author_verified %>&nbsp;<i class="icon ion ion-md-checkmark-circle"></i><% end -%>
+ </p>
+ </a>
+ <% else %>
+ <p class="channel-name" dir="auto"><%= HTML.escape(item.author) %>
+ <%- if author_verified %>&nbsp;<i class="icon ion ion-md-checkmark-circle"></i><% end -%>
+ </p>
+ <% end %>
+ </div>
</div>
<% when Category %>
<% else %>
@@ -160,11 +168,19 @@
</div>
<div class="video-card-row flexible">
- <div class="flex-left"><a href="/channel/<%= item.ucid %>">
- <p class="channel-name" dir="auto"><%= HTML.escape(item.author) %>
- <%- if author_verified %>&nbsp;<i class="icon ion ion-md-checkmark-circle"></i><% end -%>
- </p>
- </a></div>
+ <div class="flex-left">
+ <% if !item.ucid.to_s.empty? %>
+ <a href="/channel/<%= item.ucid %>">
+ <p class="channel-name" dir="auto"><%= HTML.escape(item.author) %>
+ <%- if author_verified %>&nbsp;<i class="icon ion ion-md-checkmark-circle"></i><% end -%>
+ </p>
+ </a>
+ <% else %>
+ <p class="channel-name" dir="auto"><%= HTML.escape(item.author) %>
+ <%- if author_verified %>&nbsp;<i class="icon ion ion-md-checkmark-circle"></i><% end -%>
+ </p>
+ <% end %>
+ </div>
<%= rendered "components/video-context-buttons" %>
</div>
diff --git a/src/invidious/views/template.ecr b/src/invidious/views/template.ecr
index 77265679..fd755619 100644
--- a/src/invidious/views/template.ecr
+++ b/src/invidious/views/template.ecr
@@ -1,5 +1,9 @@
+<%
+ locale = env.get("preferences").as(Preferences).locale
+ dark_mode = env.get("preferences").as(Preferences).dark_mode
+%>
<!DOCTYPE html>
-<html lang="<%= env.get("preferences").as(Preferences).locale %>">
+<html lang="<%= locale %>">
<head>
<meta charset="utf-8">
@@ -20,13 +24,8 @@
<script src="/js/_helpers.js?v=<%= ASSET_COMMIT %>"></script>
</head>
-<%
- locale = env.get("preferences").as(Preferences).locale
- dark_mode = env.get("preferences").as(Preferences).dark_mode
-%>
-
<body class="<%= dark_mode.blank? ? "no" : dark_mode %>-theme">
- <span style="display:none" id="dark_mode_pref"><%= env.get("preferences").as(Preferences).dark_mode %></span>
+ <span style="display:none" id="dark_mode_pref"><%= dark_mode %></span>
<div class="pure-g">
<div class="pure-u-1 pure-u-md-2-24"></div>
<div class="pure-u-1 pure-u-md-20-24" id="contents">
@@ -43,8 +42,8 @@
<div class="pure-u-1 pure-u-md-8-24 user-field">
<% if env.get? "user" %>
<div class="pure-u-1-4">
- <a id="toggle_theme" href="/toggle_theme?referer=<%= env.get?("current_page") %>" class="pure-menu-heading">
- <% if env.get("preferences").as(Preferences).dark_mode == "dark" %>
+ <a id="toggle_theme" href="/toggle_theme?referer=<%= env.get?("current_page") %>" class="pure-menu-heading" title="<%= translate(locale, "toggle_theme") %>">
+ <% if dark_mode == "dark" %>
<i class="icon ion-ios-sunny"></i>
<% else %>
<i class="icon ion-ios-moon"></i>
@@ -81,8 +80,8 @@
</div>
<% else %>
<div class="pure-u-1-3">
- <a id="toggle_theme" href="/toggle_theme?referer=<%= env.get?("current_page") %>" class="pure-menu-heading">
- <% if env.get("preferences").as(Preferences).dark_mode == "dark" %>
+ <a id="toggle_theme" href="/toggle_theme?referer=<%= env.get?("current_page") %>" class="pure-menu-heading" title="<%= translate(locale, "toggle_theme") %>">
+ <% if dark_mode == "dark" %>
<i class="icon ion-ios-sunny"></i>
<% else %>
<i class="icon ion-ios-moon"></i>
diff --git a/src/invidious/views/watch.ecr b/src/invidious/views/watch.ecr
index 07474896..7a1cf2c3 100644
--- a/src/invidious/views/watch.ecr
+++ b/src/invidious/views/watch.ecr
@@ -118,7 +118,7 @@ we're going to need to do it here in order to allow for translations.
link_yt_embed = URI.new(scheme: "https", host: "www.youtube.com", path: "/embed/#{video.id}")
if !plid.nil? && !continuation.nil?
- link_yt_param = URI::Params{"plid" => [plid], "index" => [continuation.to_s]}
+ link_yt_param = URI::Params{"list" => [plid], "index" => [continuation.to_s]}
link_yt_watch = IV::HttpServer::Utils.add_params_to_url(link_yt_watch, link_yt_param)
link_yt_embed = IV::HttpServer::Utils.add_params_to_url(link_yt_embed, link_yt_param)
end
@@ -346,7 +346,7 @@ we're going to need to do it here in order to allow for translations.
<h5 class="pure-g">
<div class="pure-u-14-24">
- <% if rv["ucid"]? %>
+ <% if !rv["ucid"].empty? %>
<b style="width:100%"><a href="/channel/<%= rv["ucid"] %>"><%= rv["author"]? %><% if rv["author_verified"]? == "true" %>&nbsp;<i class="icon ion ion-md-checkmark-circle"></i><% end %></a></b>
<% else %>
<b style="width:100%"><%= rv["author"]? %><% if rv["author_verified"]? == "true" %>&nbsp;<i class="icon ion ion-md-checkmark-circle"></i><% end %></b>
diff --git a/src/invidious/yt_backend/extractors.cr b/src/invidious/yt_backend/extractors.cr
index 56325cf7..0e72957e 100644
--- a/src/invidious/yt_backend/extractors.cr
+++ b/src/invidious/yt_backend/extractors.cr
@@ -822,9 +822,9 @@ module HelperExtractors
end
# Retrieves the ID required for querying the InnerTube browse endpoint.
- # Raises when it's unable to do so
+ # Returns an empty string when it's unable to do so
def self.get_browse_id(container)
- return container.dig("navigationEndpoint", "browseEndpoint", "browseId").as_s
+ return container.dig?("navigationEndpoint", "browseEndpoint", "browseId").try &.as_s || ""
end
end