mirror of
https://github.com/iv-org/invidious.git
synced 2024-12-22 16:19:53 +05:30
Merge branch 'iv-org:master' into main
This commit is contained in:
commit
a777695cf3
119
assets/css/carousel.css
Normal file
119
assets/css/carousel.css
Normal file
@ -0,0 +1,119 @@
|
||||
/*
|
||||
Copyright (c) 2024 by Jennifer (https://codepen.io/jwjertzoch/pen/JjyGeRy)
|
||||
|
||||
Permission is hereby granted, free of charge, to any person
|
||||
obtaining a copy of this software and associated documentation
|
||||
files (the "Software"), to deal in the Software without restriction,
|
||||
including without limitation the rights to use, copy, modify,
|
||||
merge, publish, distribute, sublicense, and/or sell copies of
|
||||
the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall
|
||||
be included in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
|
||||
OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
|
||||
HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
||||
DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
|
||||
.carousel {
|
||||
margin: 0 auto;
|
||||
overflow: hidden;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.slides {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
overflow-x: scroll;
|
||||
scrollbar-width: none;
|
||||
scroll-snap-type: x mandatory;
|
||||
scroll-behavior: smooth;
|
||||
}
|
||||
|
||||
.slides::-webkit-scrollbar {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.slides-item {
|
||||
align-items: center;
|
||||
border-radius: 10px;
|
||||
display: flex;
|
||||
flex-shrink: 0;
|
||||
font-size: 100px;
|
||||
height: 600px;
|
||||
justify-content: center;
|
||||
margin: 0 1rem;
|
||||
position: relative;
|
||||
scroll-snap-align: start;
|
||||
transform: scale(1);
|
||||
transform-origin: center center;
|
||||
transition: transform .5s;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.carousel__nav {
|
||||
padding: 1.25rem .5rem;
|
||||
}
|
||||
|
||||
.slider-nav {
|
||||
align-items: center;
|
||||
background-color: #ddd;
|
||||
border-radius: 50%;
|
||||
color: #000;
|
||||
display: inline-flex;
|
||||
height: 1.5rem;
|
||||
justify-content: center;
|
||||
padding: .5rem;
|
||||
position: relative;
|
||||
text-decoration: none;
|
||||
width: 1.5rem;
|
||||
}
|
||||
|
||||
.skip-link {
|
||||
height: 1px;
|
||||
overflow: hidden;
|
||||
position: absolute;
|
||||
top: auto;
|
||||
width: 1px;
|
||||
}
|
||||
|
||||
.skip-link:focus {
|
||||
align-items: center;
|
||||
background-color: #000;
|
||||
color: #fff;
|
||||
display: flex;
|
||||
font-size: 30px;
|
||||
height: 30px;
|
||||
justify-content: center;
|
||||
opacity: .8;
|
||||
text-decoration: none;
|
||||
width: 50%;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.light-theme .slider-nav {
|
||||
background-color: #ddd;
|
||||
}
|
||||
|
||||
.dark-theme .slider-nav {
|
||||
background-color: #0005;
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: light) {
|
||||
.no-theme .slider-nav {
|
||||
background-color: #ddd;
|
||||
}
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
.no-theme .slider-nav {
|
||||
background-color: #0005;
|
||||
}
|
||||
}
|
@ -13,6 +13,7 @@ body {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
min-height: 100vh;
|
||||
margin: auto;
|
||||
}
|
||||
|
||||
.h-box {
|
||||
@ -197,6 +198,7 @@ img.thumbnail {
|
||||
display: block; /* See: https://stackoverflow.com/a/11635197 */
|
||||
width: 100%;
|
||||
object-fit: cover;
|
||||
aspect-ratio: 16 / 9;
|
||||
}
|
||||
|
||||
.thumbnail-placeholder {
|
||||
|
@ -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' }
|
||||
|
@ -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
|
||||
|
@ -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": "تبديل الموضوع"
|
||||
}
|
||||
|
@ -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)"
|
||||
}
|
||||
|
@ -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)"
|
||||
}
|
||||
|
@ -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"
|
||||
}
|
||||
|
@ -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>"
|
||||
}
|
||||
|
@ -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)"
|
||||
}
|
||||
|
@ -1,4 +1,9 @@
|
||||
{
|
||||
"Add to playlist": "Add to playlist",
|
||||
"Add to playlist: ": "Add to playlist: ",
|
||||
"Answer": "Answer",
|
||||
"Search for videos": "Search for videos",
|
||||
"The Popular feed has been disabled by the administrator.": "The Popular feed has been disabled by the administrator.",
|
||||
"generic_channels_count": "{{count}} channel",
|
||||
"generic_channels_count_plural": "{{count}} channels",
|
||||
"generic_views_count": "{{count}} view",
|
||||
@ -495,5 +500,9 @@
|
||||
"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",
|
||||
"carousel_slide": "Slide {{current}} of {{total}}",
|
||||
"carousel_skip": "Skip the Carousel",
|
||||
"carousel_go_to": "Go to slide `x`"
|
||||
}
|
||||
|
109
locales/es.json
109
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"
|
||||
}
|
||||
|
@ -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>"
|
||||
}
|
||||
|
@ -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",
|
||||
|
@ -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)"
|
||||
}
|
||||
|
@ -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)"
|
||||
}
|
||||
|
@ -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)"
|
||||
}
|
||||
|
41
locales/ia.json
Normal file
41
locales/ia.json
Normal file
@ -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: "
|
||||
}
|
@ -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)"
|
||||
}
|
||||
|
@ -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)"
|
||||
}
|
||||
|
@ -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)"
|
||||
}
|
||||
|
@ -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)"
|
||||
}
|
||||
|
@ -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)"
|
||||
}
|
||||
|
@ -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)"
|
||||
}
|
||||
|
@ -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"
|
||||
}
|
||||
|
@ -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"
|
||||
}
|
||||
|
@ -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"
|
||||
}
|
||||
|
@ -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)"
|
||||
}
|
||||
|
@ -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)"
|
||||
}
|
||||
|
@ -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"
|
||||
}
|
||||
|
@ -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)"
|
||||
}
|
||||
|
@ -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": "Укључи тему"
|
||||
}
|
||||
|
@ -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"
|
||||
}
|
||||
|
@ -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"
|
||||
}
|
||||
|
@ -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": "Перемкнути тему"
|
||||
}
|
||||
|
309
locales/vi.json
309
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!"
|
||||
}
|
||||
|
@ -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": "切换主题"
|
||||
}
|
||||
|
@ -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": "切換佈景主題"
|
||||
}
|
||||
|
@ -1,34 +1,27 @@
|
||||
require "../../spec_helper.cr"
|
||||
|
||||
MockLines = [
|
||||
{
|
||||
"start_time": Time::Span.new(seconds: 1),
|
||||
"end_time": Time::Span.new(seconds: 2),
|
||||
"text": "Line 1",
|
||||
},
|
||||
|
||||
{
|
||||
"start_time": Time::Span.new(seconds: 2),
|
||||
"end_time": Time::Span.new(seconds: 3),
|
||||
"text": "Line 2",
|
||||
},
|
||||
]
|
||||
MockLines = ["Line 1", "Line 2"]
|
||||
MockLinesWithEscapableCharacter = ["<Line 1>", "&Line 2>", '\u200E' + "Line\u200F 3", "\u00A0Line 4"]
|
||||
|
||||
Spectator.describe "WebVTT::Builder" do
|
||||
it "correctly builds a vtt file" do
|
||||
result = WebVTT.build do |vtt|
|
||||
MockLines.each do |line|
|
||||
vtt.cue(line["start_time"], line["end_time"], line["text"])
|
||||
2.times do |i|
|
||||
vtt.cue(
|
||||
Time::Span.new(seconds: i),
|
||||
Time::Span.new(seconds: i + 1),
|
||||
MockLines[i]
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
expect(result).to eq([
|
||||
"WEBVTT",
|
||||
"",
|
||||
"00:00:01.000 --> 00:00:02.000",
|
||||
"00:00:00.000 --> 00:00:01.000",
|
||||
"Line 1",
|
||||
"",
|
||||
"00:00:02.000 --> 00:00:03.000",
|
||||
"00:00:01.000 --> 00:00:02.000",
|
||||
"Line 2",
|
||||
"",
|
||||
"",
|
||||
@ -42,8 +35,12 @@ Spectator.describe "WebVTT::Builder" do
|
||||
}
|
||||
|
||||
result = WebVTT.build(setting_fields) do |vtt|
|
||||
MockLines.each do |line|
|
||||
vtt.cue(line["start_time"], line["end_time"], line["text"])
|
||||
2.times do |i|
|
||||
vtt.cue(
|
||||
Time::Span.new(seconds: i),
|
||||
Time::Span.new(seconds: i + 1),
|
||||
MockLines[i]
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
@ -52,13 +49,39 @@ Spectator.describe "WebVTT::Builder" do
|
||||
"Kind: captions",
|
||||
"Language: en",
|
||||
"",
|
||||
"00:00:01.000 --> 00:00:02.000",
|
||||
"00:00:00.000 --> 00:00:01.000",
|
||||
"Line 1",
|
||||
"",
|
||||
"00:00:02.000 --> 00:00:03.000",
|
||||
"00:00:01.000 --> 00:00:02.000",
|
||||
"Line 2",
|
||||
"",
|
||||
"",
|
||||
].join('\n'))
|
||||
end
|
||||
|
||||
it "properly escapes characters" do
|
||||
result = WebVTT.build do |vtt|
|
||||
4.times do |i|
|
||||
vtt.cue(Time::Span.new(seconds: i), Time::Span.new(seconds: i + 1), MockLinesWithEscapableCharacter[i])
|
||||
end
|
||||
end
|
||||
|
||||
expect(result).to eq([
|
||||
"WEBVTT",
|
||||
"",
|
||||
"00:00:00.000 --> 00:00:01.000",
|
||||
"<Line 1>",
|
||||
"",
|
||||
"00:00:01.000 --> 00:00:02.000",
|
||||
"&Line 2>",
|
||||
"",
|
||||
"00:00:02.000 --> 00:00:03.000",
|
||||
"‎Line‏ 3",
|
||||
"",
|
||||
"00:00:03.000 --> 00:00:04.000",
|
||||
" Line 4",
|
||||
"",
|
||||
"",
|
||||
].join('\n'))
|
||||
end
|
||||
end
|
||||
|
@ -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"},
|
||||
|
@ -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'
|
||||
|
@ -33,7 +33,7 @@ module Invidious::Frontend::Comments
|
||||
<a href="javascript:void(0)" data-onclick="toggle_parent">[ − ]</a>
|
||||
<b><a href="https://www.reddit.com/user/#{child.author}">#{child.author}</a></b>
|
||||
#{translate_count(locale, "comments_points_count", child.score, NumberFormatting::Separator)}
|
||||
<span title="#{child.created_utc.to_s(translate(locale, "%a %B %-d %T %Y UTC"))}">#{translate(locale, "`x` ago", recode_date(child.created_utc, locale))}</span>
|
||||
<span title="#{child.created_utc.to_s("%a %B %-d %T %Y UTC")}">#{translate(locale, "`x` ago", recode_date(child.created_utc, locale))}</span>
|
||||
<a href="https://www.reddit.com#{child.permalink}" title="#{translate(locale, "permalink")}">#{translate(locale, "permalink")}</a>
|
||||
</p>
|
||||
<div>
|
||||
|
@ -107,6 +107,36 @@ module Invidious::Frontend::Comments
|
||||
</div>
|
||||
END_HTML
|
||||
end
|
||||
when "multiImage"
|
||||
html << <<-END_HTML
|
||||
<section class="carousel">
|
||||
<a class="skip-link" href="#skip-#{child["commentId"]}">#{translate(locale, "carousel_skip")}</a>
|
||||
<div class="slides">
|
||||
END_HTML
|
||||
image_array = attachment["images"].as_a
|
||||
|
||||
image_array.each_index do |i|
|
||||
html << <<-END_HTML
|
||||
<div class="slides-item slide-#{i + 1}" id="#{child["commentId"]}-slide-#{i + 1}" aria-label="#{translate(locale, "carousel_slide", {"current" => (i + 1).to_s, "total" => image_array.size.to_s})}" tabindex="0">
|
||||
<img loading="lazy" src="/ggpht#{URI.parse(image_array[i][1]["url"].as_s).request_target}" alt="" />
|
||||
</div>
|
||||
END_HTML
|
||||
end
|
||||
|
||||
html << <<-END_HTML
|
||||
</div>
|
||||
<div class="carousel__nav">
|
||||
END_HTML
|
||||
attachment["images"].as_a.each_index do |i|
|
||||
html << <<-END_HTML
|
||||
<a class="slider-nav" href="##{child["commentId"]}-slide-#{i + 1}" aria-label="#{translate(locale, "carousel_go_to", (i + 1).to_s)}" tabindex="-1" aria-hidden="true">#{i + 1}</a>
|
||||
END_HTML
|
||||
end
|
||||
html << <<-END_HTML
|
||||
</div>
|
||||
<div id="skip-#{child["commentId"]}"></div>
|
||||
</section>
|
||||
END_HTML
|
||||
else nil # Ignore
|
||||
end
|
||||
end
|
||||
|
@ -142,63 +142,8 @@ class APIHandler < Kemal::Handler
|
||||
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
|
||||
env.response.headers["Access-Control-Allow-Origin"] = "*" if only_match?(env)
|
||||
call_next env
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -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
|
||||
|
@ -78,7 +78,7 @@ def load_all_locales
|
||||
return locales
|
||||
end
|
||||
|
||||
def translate(locale : String?, key : String, text : String | Nil = nil) : String
|
||||
def translate(locale : String?, key : String, text : String | Hash(String, String) | Nil = nil) : String
|
||||
# Log a warning if "key" doesn't exist in en-US locale and return
|
||||
# that key as the text, so this is more or less transparent to the user.
|
||||
if !LOCALES["en-US"].has_key?(key)
|
||||
@ -101,10 +101,12 @@ def translate(locale : String?, key : String, text : String | Nil = nil) : Strin
|
||||
match_length = 0
|
||||
|
||||
raw_data.as_h.each do |hash_key, value|
|
||||
if md = text.try &.match(/#{hash_key}/)
|
||||
if md[0].size >= match_length
|
||||
translation = value.as_s
|
||||
match_length = md[0].size
|
||||
if text.is_a?(String)
|
||||
if md = text.try &.match(/#{hash_key}/)
|
||||
if md[0].size >= match_length
|
||||
translation = value.as_s
|
||||
match_length = md[0].size
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@ -114,8 +116,13 @@ def translate(locale : String?, key : String, text : String | Nil = nil) : Strin
|
||||
raise "Invalid translation \"#{raw_data}\""
|
||||
end
|
||||
|
||||
if text
|
||||
if text.is_a?(String)
|
||||
translation = translation.gsub("`x`", text)
|
||||
elsif text.is_a?(Hash(String, String))
|
||||
# adds support for multi string interpolation. Based on i18next https://www.i18next.com/translation-function/interpolation#basic
|
||||
text.each_key do |hash_key|
|
||||
translation = translation.gsub("{{#{hash_key}}}", text[hash_key])
|
||||
end
|
||||
end
|
||||
|
||||
return translation
|
||||
|
@ -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
|
||||
|
||||
|
@ -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
|
@ -264,7 +264,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
|
||||
|
@ -4,13 +4,23 @@
|
||||
module WebVTT
|
||||
# A WebVTT builder generates WebVTT files
|
||||
private class Builder
|
||||
# See https://developer.mozilla.org/en-US/docs/Web/API/WebVTT_API#cue_payload
|
||||
private ESCAPE_SUBSTITUTIONS = {
|
||||
'&' => "&",
|
||||
'<' => "<",
|
||||
'>' => ">",
|
||||
'\u200E' => "‎",
|
||||
'\u200F' => "‏",
|
||||
'\u00A0' => " ",
|
||||
}
|
||||
|
||||
def initialize(@io : IO)
|
||||
end
|
||||
|
||||
# Writes an vtt cue with the specified time stamp and contents
|
||||
def cue(start_time : Time::Span, end_time : Time::Span, text : String)
|
||||
timestamp(start_time, end_time)
|
||||
@io << text
|
||||
@io << self.escape(text)
|
||||
@io << "\n\n"
|
||||
end
|
||||
|
||||
@ -29,6 +39,10 @@ module WebVTT
|
||||
@io << '.' << timestamp.milliseconds.to_s.rjust(3, '0')
|
||||
end
|
||||
|
||||
private def escape(text : String) : String
|
||||
return text.gsub(ESCAPE_SUBSTITUTIONS)
|
||||
end
|
||||
|
||||
def document(setting_fields : Hash(String, String)? = nil, &)
|
||||
@io << "WEBVTT\n"
|
||||
|
||||
|
@ -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,
|
||||
|
@ -257,6 +257,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
|
||||
|
@ -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|
|
||||
|
@ -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
|
||||
|
@ -437,14 +437,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)
|
||||
# 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)
|
||||
|
||||
video = get_video(id, force_refresh: true)
|
||||
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`
|
||||
|
@ -310,6 +310,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")
|
||||
|
@ -251,6 +251,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
|
||||
|
22
src/invidious/videos/clip.cr
Normal file
22
src/invidious/videos/clip.cr
Normal file
@ -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
|
@ -84,11 +84,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 %> <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 %> <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 %> <i class="icon ion ion-md-checkmark-circle"></i><% end -%>
|
||||
</p>
|
||||
<% end %>
|
||||
</div>
|
||||
</div>
|
||||
<% when Category %>
|
||||
<% else %>
|
||||
@ -170,11 +178,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 %> <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 %> <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 %> <i class="icon ion ion-md-checkmark-circle"></i><% end -%>
|
||||
</p>
|
||||
<% end %>
|
||||
</div>
|
||||
|
||||
<%= rendered "components/video-context-buttons" %>
|
||||
</div>
|
||||
|
@ -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">
|
||||
@ -17,19 +21,14 @@
|
||||
<link rel="stylesheet" href="/css/grids-responsive-min.css?v=<%= ASSET_COMMIT %>">
|
||||
<link rel="stylesheet" href="/css/ionicons.min.css?v=<%= ASSET_COMMIT %>">
|
||||
<link rel="stylesheet" href="/css/default.css?v=<%= ASSET_COMMIT %>">
|
||||
<link rel="stylesheet" href="/css/carousel.css?v=<%= ASSET_COMMIT %>">
|
||||
<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">
|
||||
<div class="pure-u-1 pure-u-xl-20-24" id="contents">
|
||||
<div class="pure-g navbar h-box">
|
||||
<% if navbar_search %>
|
||||
<div class="pure-u-1 pure-u-md-4-24">
|
||||
@ -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>
|
||||
@ -156,7 +155,6 @@
|
||||
</footer>
|
||||
|
||||
</div>
|
||||
<div class="pure-u-1 pure-u-md-2-24"></div>
|
||||
</div>
|
||||
<script src="/js/handlers.js?v=<%= ASSET_COMMIT %>"></script>
|
||||
<script src="/js/themes.js?v=<%= ASSET_COMMIT %>"></script>
|
||||
|
@ -121,7 +121,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
|
||||
@ -379,7 +379,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" %> <i class="icon ion ion-md-checkmark-circle"></i><% end %></a></b>
|
||||
<% else %>
|
||||
<b style="width:100%"><%= rv["author"]? %><% if rv["author_verified"]? == "true" %> <i class="icon ion ion-md-checkmark-circle"></i><% end %></b>
|
||||
|
@ -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
|
||||
|
||||
|
@ -7,17 +7,18 @@ module YoutubeAPI
|
||||
|
||||
private DEFAULT_API_KEY = "AIzaSyAO_FJ2SlqU8Q4STEHLGCilw_Y9_11qcW8"
|
||||
|
||||
private ANDROID_APP_VERSION = "18.20.38"
|
||||
# github.com/TeamNewPipe/NewPipeExtractor/blob/943b7c033bb9d07ead63ddab4441c287653e4384/extractor/src/main/java/org/schabi/newpipe/extractor/services/youtube/YoutubeParsingHelper.java#L1308
|
||||
private ANDROID_USER_AGENT = "com.google.android.youtube/18.20.38 (Linux; U; Android 12; US) gzip"
|
||||
# For Android versions, see https://en.wikipedia.org/wiki/Android_version_history
|
||||
private ANDROID_APP_VERSION = "19.09.36"
|
||||
private ANDROID_USER_AGENT = "com.google.android.youtube/19.09.36 (Linux; U; Android 12; US) gzip"
|
||||
private ANDROID_SDK_VERSION = 31_i64
|
||||
private ANDROID_VERSION = "12"
|
||||
|
||||
private IOS_APP_VERSION = "18.21.3"
|
||||
# github.com/TeamNewPipe/NewPipeExtractor/blob/943b7c033bb9d07ead63ddab4441c287653e4384/extractor/src/main/java/org/schabi/newpipe/extractor/services/youtube/YoutubeParsingHelper.java#L1330
|
||||
private IOS_USER_AGENT = "com.google.ios.youtube/18.21.3 (iPhone14,5; U; CPU iOS 15_6 like Mac OS X;)"
|
||||
# github.com/TeamNewPipe/NewPipeExtractor/blob/943b7c033bb9d07ead63ddab4441c287653e4384/extractor/src/main/java/org/schabi/newpipe/extractor/services/youtube/YoutubeParsingHelper.java#L1224
|
||||
private IOS_VERSION = "15.6.0.19G71"
|
||||
# For Apple device names, see https://gist.github.com/adamawolf/3048717
|
||||
# For iOS versions, see https://en.wikipedia.org/wiki/IOS_version_history#Releases,
|
||||
# then go to the dedicated article of the major version you want.
|
||||
private IOS_APP_VERSION = "19.09.3"
|
||||
private IOS_USER_AGENT = "com.google.ios.youtube/19.09.3 (iPhone14,5; U; CPU iOS 17_4 like Mac OS X;)"
|
||||
private IOS_VERSION = "17.4.0.21E219" # Major.Minor.Patch.Build
|
||||
|
||||
private WINDOWS_VERSION = "10.0"
|
||||
|
||||
@ -45,7 +46,7 @@ module YoutubeAPI
|
||||
ClientType::Web => {
|
||||
name: "WEB",
|
||||
name_proto: "1",
|
||||
version: "2.20230602.01.00",
|
||||
version: "2.20240304.00.00",
|
||||
api_key: DEFAULT_API_KEY,
|
||||
screen: "WATCH_FULL_SCREEN",
|
||||
os_name: "Windows",
|
||||
@ -55,7 +56,7 @@ module YoutubeAPI
|
||||
ClientType::WebEmbeddedPlayer => {
|
||||
name: "WEB_EMBEDDED_PLAYER",
|
||||
name_proto: "56",
|
||||
version: "1.20220803.01.00",
|
||||
version: "1.20240303.00.00",
|
||||
api_key: DEFAULT_API_KEY,
|
||||
screen: "EMBED",
|
||||
os_name: "Windows",
|
||||
@ -65,7 +66,7 @@ module YoutubeAPI
|
||||
ClientType::WebMobile => {
|
||||
name: "MWEB",
|
||||
name_proto: "2",
|
||||
version: "2.20230531.05.00",
|
||||
version: "2.20240304.08.00",
|
||||
api_key: DEFAULT_API_KEY,
|
||||
os_name: "Android",
|
||||
os_version: ANDROID_VERSION,
|
||||
@ -74,7 +75,7 @@ module YoutubeAPI
|
||||
ClientType::WebScreenEmbed => {
|
||||
name: "WEB",
|
||||
name_proto: "1",
|
||||
version: "2.20220804.00.00",
|
||||
version: "2.20240304.00.00",
|
||||
api_key: DEFAULT_API_KEY,
|
||||
screen: "EMBED",
|
||||
os_name: "Windows",
|
||||
@ -99,7 +100,7 @@ module YoutubeAPI
|
||||
name: "ANDROID_EMBEDDED_PLAYER",
|
||||
name_proto: "55",
|
||||
version: ANDROID_APP_VERSION,
|
||||
api_key: DEFAULT_API_KEY,
|
||||
api_key: "AIzaSyCjc_pVEDi4qsv5MtC2dMXzpIaDoRFLsxw",
|
||||
},
|
||||
ClientType::AndroidScreenEmbed => {
|
||||
name: "ANDROID",
|
||||
@ -143,9 +144,9 @@ module YoutubeAPI
|
||||
ClientType::IOSMusic => {
|
||||
name: "IOS_MUSIC",
|
||||
name_proto: "26",
|
||||
version: "5.21",
|
||||
version: "6.42",
|
||||
api_key: "AIzaSyBAETezhkwP0ZWA02RsqT1zu78Fpt0bC_s",
|
||||
user_agent: "com.google.ios.youtubemusic/5.21 (iPhone14,5; U; CPU iOS 15_6 like Mac OS X;)",
|
||||
user_agent: "com.google.ios.youtubemusic/6.42 (iPhone14,5; U; CPU iOS 17_4 like Mac OS X;)",
|
||||
device_make: "Apple",
|
||||
device_model: "iPhone14,5",
|
||||
os_name: "iPhone",
|
||||
@ -158,7 +159,7 @@ module YoutubeAPI
|
||||
ClientType::TvHtml5 => {
|
||||
name: "TVHTML5",
|
||||
name_proto: "7",
|
||||
version: "7.20220325",
|
||||
version: "7.20240304.10.00",
|
||||
api_key: DEFAULT_API_KEY,
|
||||
},
|
||||
ClientType::TvHtml5ScreenEmbed => {
|
||||
|
Loading…
Reference in New Issue
Block a user