diff options
61 files changed, 377 insertions, 198 deletions
diff --git a/docker/Dockerfile b/docker/Dockerfile index 34549df1..57864883 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -43,7 +43,7 @@ RUN if [[ "${release}" == 1 && "${disable_quic}" == 1 ]] ; then \ FROM alpine:3.16 -RUN apk add --no-cache librsvg ttf-opensans +RUN apk add --no-cache librsvg ttf-opensans tini WORKDIR /invidious RUN addgroup -g 1000 -S invidious && \ adduser -u 1000 -S invidious -G invidious @@ -58,4 +58,5 @@ RUN chmod o+rX -R ./assets ./config ./locales EXPOSE 3000 USER invidious +ENTRYPOINT ["/sbin/tini", "--"] CMD [ "/invidious/invidious" ] diff --git a/docker/Dockerfile.arm64 b/docker/Dockerfile.arm64 index ef3284b1..10135efb 100644 --- a/docker/Dockerfile.arm64 +++ b/docker/Dockerfile.arm64 @@ -42,7 +42,7 @@ RUN if [[ "${release}" == 1 && "${disable_quic}" == 1 ]] ; then \ fi FROM alpine:3.16 -RUN apk add --no-cache librsvg ttf-opensans +RUN apk add --no-cache librsvg ttf-opensans tini WORKDIR /invidious RUN addgroup -g 1000 -S invidious && \ adduser -u 1000 -S invidious -G invidious @@ -57,4 +57,5 @@ RUN chmod o+rX -R ./assets ./config ./locales EXPOSE 3000 USER invidious +ENTRYPOINT ["/sbin/tini", "--"] CMD [ "/invidious/invidious" ] diff --git a/kubernetes/Chart.lock b/kubernetes/Chart.lock index 37fcdbbd..cc76e920 100644 --- a/kubernetes/Chart.lock +++ b/kubernetes/Chart.lock @@ -1,6 +1,6 @@ dependencies: - name: postgresql repository: https://charts.bitnami.com/bitnami/ - version: 11.1.3 -digest: sha256:79061645472b6fb342d45e8e5b3aacd018ef5067193e46a060bccdc99fe7f6e1 -generated: "2022-03-02T05:57:20.081432389+13:00" + version: 12.1.9 +digest: sha256:71ff342a6c0a98bece3d7fe199983afb2113f8db65a3e3819de875af2c45add7 +generated: "2023-01-20T20:42:32.757707004Z" diff --git a/locales/ar.json b/locales/ar.json index 2a746e5d..55dea5f3 100644 --- a/locales/ar.json +++ b/locales/ar.json @@ -325,9 +325,9 @@ "`x` marked it with a ❤": "`x` أعجب بهذا", "Audio mode": "الوضع الصوتي", "Video mode": "وضع الفيديو", - "Videos": "الفيديوهات", + "channel_tab_videos_label": "الفيديوهات", "Playlists": "قوائم التشغيل", - "Community": "المجتمع", + "channel_tab_community_label": "المجتمع", "search_filters_sort_option_relevance": "ملائمة", "search_filters_sort_option_rating": "تقييم", "search_filters_sort_option_date": "التاريخ", @@ -536,5 +536,9 @@ "generic_count_seconds_3": "{{count}} ثوانٍ", "generic_count_seconds_4": "{{count}} ثانية", "generic_count_seconds_5": "{{count}} ثانية", - "error_video_not_in_playlist": "الفيديو المطلوب غير موجود في قائمة التشغيل هذه. <a href=\"`x`\"> انقر هنا للحصول على الصفحة الرئيسية لقائمة التشغيل. </a>" + "error_video_not_in_playlist": "الفيديو المطلوب غير موجود في قائمة التشغيل هذه. <a href=\"`x`\"> انقر هنا للحصول على الصفحة الرئيسية لقائمة التشغيل. </a>", + "channel_tab_shorts_label": "الفيديوهات القصيرة", + "channel_tab_streams_label": "البث المباشر", + "channel_tab_playlists_label": "قوائم التشغيل", + "channel_tab_channels_label": "القنوات" } diff --git a/locales/ca.json b/locales/ca.json index 741414d2..2ba6ae39 100644 --- a/locales/ca.json +++ b/locales/ca.json @@ -51,7 +51,7 @@ "Movies": "Películes", "Download": "Descarrega", "Download as: ": "Descarrega com: ", - "Videos": "Vídeos", + "channel_tab_videos_label": "Vídeos", "search_filters_type_label": "Tipus", "search_filters_duration_label": "Duració", "search_filters_sort_label": "Ordena per", diff --git a/locales/cs.json b/locales/cs.json index 7538365a..7502de0b 100644 --- a/locales/cs.json +++ b/locales/cs.json @@ -63,7 +63,7 @@ "reddit": "Reddit", "preferences_captions_label": "Výchozí titulky: ", "Fallback captions: ": "Záložní titulky: ", - "preferences_related_videos_label": "Zobrazit podobné videa: ", + "preferences_related_videos_label": "Zobrazit podobná videa: ", "preferences_annotations_label": "Zobrazovat poznámky ve výchozím nastavení: ", "preferences_extend_desc_label": "Rozšířit automaticky popis u videa: ", "preferences_category_visual": "Nastavení vzhledu", @@ -260,8 +260,8 @@ "`x` marked it with a ❤": "`x` to označil(a) se ❤", "Audio mode": "Audiový režim", "Video mode": "Videový režim", - "Videos": "Videa", - "Community": "Komunita", + "channel_tab_videos_label": "Videa", + "channel_tab_community_label": "Komunita", "search_filters_sort_option_rating": "Hodnocení", "search_filters_sort_option_date": "Datum nahrání", "search_filters_sort_option_views": "Počet zhlédnutí", @@ -488,5 +488,9 @@ "search_filters_sort_option_relevance": "Relevantnost", "search_filters_apply_button": "Použít vybrané filtry", "Popular enabled: ": "Populární povoleno: ", - "error_video_not_in_playlist": "Požadované video v tomto playlistu neexistuje. <a href=\"`x`\">Klikněte sem pro navštívení domovské stránky playlistu.</a>" + "error_video_not_in_playlist": "Požadované video v tomto playlistu neexistuje. <a href=\"`x`\">Klikněte sem pro navštívení domovské stránky playlistu.</a>", + "channel_tab_shorts_label": "Shorts", + "channel_tab_playlists_label": "Playlisty", + "channel_tab_channels_label": "Kanály", + "channel_tab_streams_label": "Živé přenosy" } diff --git a/locales/da.json b/locales/da.json index 4816c2c9..2bee6c80 100644 --- a/locales/da.json +++ b/locales/da.json @@ -187,7 +187,7 @@ "Esperanto": "Esperanto", "Czech": "Tjekkisk", "Danish": "Dansk", - "Community": "Samfund", + "channel_tab_community_label": "Samfund", "Afrikaans": "Afrikansk", "Portuguese": "Portugisisk", "Ukrainian": "Ukrainsk", @@ -267,7 +267,7 @@ "search_filters_sort_option_rating": "Bedømmelse", "Yoruba": "Yoruba", "Erroneous token": "Fejlagtig token", - "Videos": "Videoer", + "channel_tab_videos_label": "Videoer", "search_filters_type_option_show": "Vis", "Luxembourgish": "Luxemboursk", "Vietnamese": "Vietnamesisk", diff --git a/locales/de.json b/locales/de.json index a2070cf5..55c40905 100644 --- a/locales/de.json +++ b/locales/de.json @@ -325,9 +325,9 @@ "`x` marked it with a ❤": "`x` markierte es mit einem ❤", "Audio mode": "Audiomodus", "Video mode": "Videomodus", - "Videos": "Videos", + "channel_tab_videos_label": "Videos", "Playlists": "Wiedergabelisten", - "Community": "Gemeinschaft", + "channel_tab_community_label": "Gemeinschaft", "search_filters_sort_option_relevance": "Relevanz", "search_filters_sort_option_rating": "Bewertung", "search_filters_sort_option_date": "Datum", diff --git a/locales/el.json b/locales/el.json index d91d64fc..3448a4dc 100644 --- a/locales/el.json +++ b/locales/el.json @@ -315,9 +315,9 @@ "`x` marked it with a ❤": "Ο χρηστης `x` έβαλε ❤", "Audio mode": "Λειτουργία ήχου", "Video mode": "Λειτουργία βίντεο", - "Videos": "Βίντεο", + "channel_tab_videos_label": "Βίντεο", "Playlists": "Λίστες Αναπαραγωγής", - "Community": "Κοινότητα", + "channel_tab_community_label": "Κοινότητα", "Current version: ": "Τρέχουσα έκδοση: ", "generic_playlists_count": "{{count}} λίστα αναπαραγωγής", "generic_playlists_count_plural": "{{count}} λίστες αναπαραγωγής", diff --git a/locales/eo.json b/locales/eo.json index 5aa2bbc6..56e718f2 100644 --- a/locales/eo.json +++ b/locales/eo.json @@ -325,9 +325,9 @@ "`x` marked it with a ❤": "`x` markis ĝin per ❤", "Audio mode": "Aŭda reĝimo", "Video mode": "Videa reĝimo", - "Videos": "Filmetoj", + "channel_tab_videos_label": "Videoj", "Playlists": "Ludlistoj", - "Community": "Komunumo", + "channel_tab_community_label": "Komunumo", "search_filters_sort_option_relevance": "rilateco", "search_filters_sort_option_rating": "takso", "search_filters_sort_option_date": "dato", @@ -472,5 +472,9 @@ "generic_subscribers_count_plural": "{{count}} abonantoj", "generic_count_months": "{{count}} monato", "generic_count_months_plural": "{{count}} monatoj", - "preferences_save_player_pos_label": "Konservi ludadan pozicion: " + "preferences_save_player_pos_label": "Konservi ludadan pozicion: ", + "channel_tab_streams_label": "Tujelsendoj", + "channel_tab_playlists_label": "Ludlistoj", + "channel_tab_channels_label": "Kanaloj", + "channel_tab_shorts_label": "Mallongaj" } diff --git a/locales/es.json b/locales/es.json index 8603e9fe..59d6b145 100644 --- a/locales/es.json +++ b/locales/es.json @@ -325,9 +325,9 @@ "`x` marked it with a ❤": "`x` lo ha marcado con un ❤", "Audio mode": "Modo de audio", "Video mode": "Modo de vídeo", - "Videos": "Vídeos", + "channel_tab_videos_label": "Vídeos", "Playlists": "Listas de reproducción", - "Community": "Comunidad", + "channel_tab_community_label": "Comunidad", "search_filters_sort_option_relevance": "relevancia", "search_filters_sort_option_rating": "valoración", "search_filters_sort_option_date": "fecha", @@ -472,5 +472,9 @@ "search_message_use_another_instance": " También puede <a href=\"`x`\">buscar en otra instancia</a>.", "search_filters_duration_option_medium": "Medio (4 - 20 minutes)", "Popular enabled: ": "¿Habilitar la sección popular? ", - "error_video_not_in_playlist": "El vídeo solicitado no existe en esta lista de reproducción. <a href=\"`x`\">Haga clic aquí para acceder a la página de inicio de la lista de reproducción.</a>" + "error_video_not_in_playlist": "El vídeo solicitado no existe en esta lista de reproducción. <a href=\"`x`\">Haga clic aquí para acceder a la página de inicio de la lista de reproducción.</a>", + "channel_tab_streams_label": "Directos", + "channel_tab_channels_label": "Canales", + "channel_tab_shorts_label": "Cortos", + "channel_tab_playlists_label": "Listas de reproducción" } diff --git a/locales/et.json b/locales/et.json index 7beb1749..74338aba 100644 --- a/locales/et.json +++ b/locales/et.json @@ -296,8 +296,8 @@ "Corsican": "Korsika", "Javanese": "Jaava", "Lithuanian": "Leedu", - "Videos": "Videod", - "Community": "Kogukond", + "channel_tab_videos_label": "Videod", + "channel_tab_community_label": "Kogukond", "CAPTCHA is a required field": "CAPTCHA on kohustuslik väli", "comments_points_count": "{{count}} punkt", "comments_points_count_plural": "{{count}} punkti", diff --git a/locales/fa.json b/locales/fa.json index 3a8f547f..f2ca2745 100644 --- a/locales/fa.json +++ b/locales/fa.json @@ -341,9 +341,9 @@ "`x` marked it with a ❤": "`x` نشان گذاری شده با یک ❤", "Audio mode": "حالت صدا", "Video mode": "حالت ویدیو", - "Videos": "ویدیو ها", + "channel_tab_videos_label": "ویدیو ها", "Playlists": "سیاهههای پخش", - "Community": "اجتماع", + "channel_tab_community_label": "اجتماع", "search_filters_sort_option_relevance": "مرتبط بودن", "search_filters_sort_option_rating": "امتیاز", "search_filters_sort_option_date": "تاریخ بارگذاری", diff --git a/locales/fi.json b/locales/fi.json index bef9027f..366a2739 100644 --- a/locales/fi.json +++ b/locales/fi.json @@ -324,9 +324,9 @@ "`x` marked it with a ❤": "`x` merkkasi ❤:llä", "Audio mode": "Äänitila", "Video mode": "Videotila", - "Videos": "Videot", + "channel_tab_videos_label": "Videot", "Playlists": "Soittolistat", - "Community": "Yhteisö", + "channel_tab_community_label": "Yhteisö", "search_filters_sort_option_relevance": "Osuvuus", "search_filters_sort_option_rating": "Arvostelu", "search_filters_sort_option_date": "Latauspäivämäärä", diff --git a/locales/fr.json b/locales/fr.json index 2f384eb1..9d3e117f 100644 --- a/locales/fr.json +++ b/locales/fr.json @@ -358,9 +358,9 @@ "`x` marked it with a ❤": "`x` l'a marqué d'un ❤", "Audio mode": "Mode audio", "Video mode": "Mode vidéo", - "Videos": "Vidéos", + "channel_tab_videos_label": "Vidéos", "Playlists": "Listes de lecture", - "Community": "Communauté", + "channel_tab_community_label": "Communauté", "search_filters_sort_option_relevance": "Pertinence", "search_filters_sort_option_rating": "Notation", "search_filters_sort_option_date": "Date d'ajout", @@ -472,5 +472,9 @@ "search_filters_date_label": "Date d'ajout", "search_filters_features_option_vr180": "VR180", "search_filters_duration_option_none": "Toutes les durées", - "error_video_not_in_playlist": "La vidéo demandée n'existe pas dans cette liste de lecture. <a href=\"`x`\">Cliquez ici pour retourner à la liste de lecture.</a>" + "error_video_not_in_playlist": "La vidéo demandée n'existe pas dans cette liste de lecture. <a href=\"`x`\">Cliquez ici pour retourner à la liste de lecture.</a>", + "channel_tab_shorts_label": "Clips", + "channel_tab_streams_label": "En direct", + "channel_tab_playlists_label": "Listes de lecture", + "channel_tab_channels_label": "Chaînes" } diff --git a/locales/he.json b/locales/he.json index 384b2657..ab42313b 100644 --- a/locales/he.json +++ b/locales/he.json @@ -271,9 +271,9 @@ "`x` marked it with a ❤": "סומנה ב־❤ על ידי `x`", "Audio mode": "Audio mode", "Video mode": "Video mode", - "Videos": "סרטונים", + "channel_tab_videos_label": "סרטונים", "Playlists": "פלייליסטים", - "Community": "קהילה", + "channel_tab_community_label": "קהילה", "search_filters_sort_option_relevance": "רלוונטיות", "search_filters_sort_option_rating": "דירוג", "search_filters_sort_option_date": "תאריך העלאה", diff --git a/locales/hi.json b/locales/hi.json index 32ae7823..e576080f 100644 --- a/locales/hi.json +++ b/locales/hi.json @@ -401,12 +401,12 @@ "(edited)": "(संपादित)", "YouTube comment permalink": "YouTube पर टिप्पणी की स्थायी कड़ी", "permalink": "स्थायी कड़ी", - "Videos": "वीडियो", + "channel_tab_videos_label": "वीडियो", "`x` marked it with a ❤": "`x` ने इसे एक ❤ से चिह्नित किया", "Audio mode": "ऑडियो मोड", "Playlists": "प्लेलिस्ट्स", "Video mode": "वीडियो मोड", - "Community": "समुदाय", + "channel_tab_community_label": "समुदाय", "search_filters_title": "फ़िल्टर", "search_filters_date_label": "अपलोड करने का समय", "search_filters_date_option_none": "कोई भी समय", diff --git a/locales/hr.json b/locales/hr.json index e42cc4f5..7914ab16 100644 --- a/locales/hr.json +++ b/locales/hr.json @@ -7,8 +7,8 @@ "View playlist on YouTube": "Prikaži zbirku na YouTubeu", "newest": "najnovije", "oldest": "najstarije", - "popular": "popularni", - "last": "zadnji", + "popular": "popularne", + "last": "zadnje", "Next page": "Sljedeća stranica", "Previous page": "Prethodna stranica", "Clear watch history?": "Izbrisati povijest gledanja?", @@ -43,9 +43,9 @@ "Time (h:mm:ss):": "Vrijeme (h:mm:ss):", "Text CAPTCHA": "Tekstualni CAPTCHA", "Image CAPTCHA": "Slikovni CAPTCHA", - "Sign In": "Prijava", + "Sign In": "Prijavi se", "Register": "Registriraj se", - "E-mail": "E-mail", + "E-mail": "E-mail adresa", "Google verification code": "Googleov potvrdni kod", "Preferences": "Postavke", "preferences_category_player": "Postavke playera", @@ -325,9 +325,9 @@ "`x` marked it with a ❤": "Označeno sa ❤ od `x`", "Audio mode": "Audio modus", "Video mode": "Videomodus", - "Videos": "Videa", + "channel_tab_videos_label": "Videa", "Playlists": "Zbirke", - "Community": "Zajednica", + "channel_tab_community_label": "Zajednica", "search_filters_sort_option_relevance": "Značaj", "search_filters_sort_option_rating": "Ocjena", "search_filters_sort_option_date": "Datum prijenosa", @@ -488,5 +488,9 @@ "search_filters_apply_button": "Primijeni odabrane filtre", "search_filters_type_option_all": "Bilo koja vrsta", "Popular enabled: ": "Popularni aktivirani: ", - "error_video_not_in_playlist": "Traženi video ne postoji u ovoj zbirci. <a href=\"`x`\">Pritisni ovdje za početnu stranicu zbirke.</a>" + "error_video_not_in_playlist": "Traženi video ne postoji u ovoj zbirci. <a href=\"`x`\">Pritisni ovdje za početnu stranicu zbirke.</a>", + "channel_tab_streams_label": "Prijenosi uživo", + "channel_tab_playlists_label": "Zbirke", + "channel_tab_channels_label": "Kanali", + "channel_tab_shorts_label": "Kratka videa" } diff --git a/locales/hu-HU.json b/locales/hu-HU.json index 19ada1d8..f93930e0 100644 --- a/locales/hu-HU.json +++ b/locales/hu-HU.json @@ -348,9 +348,9 @@ "`x` marked it with a ❤": "`x` ❤ jelet adott a hozzászóláshoz", "Audio mode": "Csak hanggal", "Video mode": "Hanggal és képpel", - "Videos": "Videói", + "channel_tab_videos_label": "Videói", "Playlists": "Lejátszási listái", - "Community": "Közösség", + "channel_tab_community_label": "Közösség", "Current version: ": "Jelenlegi verzió: ", "preferences_quality_option_medium": "Közepes", "preferences_quality_dash_option_auto": "Automatikus", diff --git a/locales/id.json b/locales/id.json index a30f0ad4..51d6d55c 100644 --- a/locales/id.json +++ b/locales/id.json @@ -341,9 +341,9 @@ "`x` marked it with a ❤": "`x` telah ditandai dengan ❤", "Audio mode": "Mode audio", "Video mode": "Mode video", - "Videos": "Video", + "channel_tab_videos_label": "Video", "Playlists": "Daftar putar", - "Community": "Komunitas", + "channel_tab_community_label": "Komunitas", "search_filters_sort_option_relevance": "Relevansi", "search_filters_sort_option_rating": "Penilaian", "search_filters_sort_option_date": "Tanggal Unggah", diff --git a/locales/is.json b/locales/is.json index 99bd6574..3282eb50 100644 --- a/locales/is.json +++ b/locales/is.json @@ -315,9 +315,9 @@ "`x` marked it with a ❤": "`x` merkti það með ❤", "Audio mode": "Hljóð ham", "Video mode": "Myndband ham", - "Videos": "Myndbönd", + "channel_tab_videos_label": "Myndbönd", "Playlists": "Spilunarlistar", - "Community": "Samfélag", + "channel_tab_community_label": "Samfélag", "Current version: ": "Núverandi útgáfa: ", "preferences_watch_history_label": "Virkja áhorfssögu: " } diff --git a/locales/it.json b/locales/it.json index c195f3b9..f47b032e 100644 --- a/locales/it.json +++ b/locales/it.json @@ -344,9 +344,8 @@ "`x` marked it with a ❤": "`x` l'ha contrassegnato con un ❤", "Audio mode": "Modalità audio", "Video mode": "Modalità video", - "Videos": "Video", + "channel_tab_videos_label": "Video", "Playlists": "Playlist", - "Community": "Comunità", "search_filters_sort_option_relevance": "Pertinenza", "search_filters_sort_option_rating": "Valutazione", "search_filters_sort_option_date": "Data di caricamento", @@ -472,5 +471,10 @@ "search_filters_features_option_vr180": "VR180", "search_filters_apply_button": "Applica filtri selezionati", "crash_page_refresh": "provato a <a href=\"`x`\">ricaricare la pagina</a>", - "error_video_not_in_playlist": "Il video richiesto non esiste in questa playlist. <a href=\"`x`\">Fai clic qui per la pagina iniziale della playlist.</a>" + "error_video_not_in_playlist": "Il video richiesto non esiste in questa playlist. <a href=\"`x`\">Fai clic qui per la pagina iniziale della playlist.</a>", + "channel_tab_shorts_label": "Short", + "channel_tab_playlists_label": "Playlist", + "channel_tab_channels_label": "Canali", + "channel_tab_streams_label": "Livestream", + "channel_tab_community_label": "Comunità" } diff --git a/locales/ja.json b/locales/ja.json index 4971c472..a392abfe 100644 --- a/locales/ja.json +++ b/locales/ja.json @@ -341,9 +341,9 @@ "`x` marked it with a ❤": "`x` が❤を込めてマークしました", "Audio mode": "オーディオモード", "Video mode": "ビデオモード", - "Videos": "動画", + "channel_tab_videos_label": "動画", "Playlists": "プレイリスト", - "Community": "コミュニティ", + "channel_tab_community_label": "コミュニティ", "search_filters_sort_option_relevance": "関連", "search_filters_sort_option_rating": "評価", "search_filters_sort_option_date": "時刻", diff --git a/locales/ko.json b/locales/ko.json index 28b518a2..d4f3a711 100644 --- a/locales/ko.json +++ b/locales/ko.json @@ -2,7 +2,7 @@ "preferences_sort_label": "동영상 정렬 기준: ", "preferences_max_results_label": "피드에 표시된 동영상 수: ", "Redirect homepage to feed: ": "피드로 홈페이지 리디렉션: ", - "preferences_annotations_subscribed_label": "구독한 채널에 기본적으로 특수효과를 표시하시겠습니까? ", + "preferences_annotations_subscribed_label": "구독한 채널에 기본으로 주석 표시: ", "preferences_category_subscription": "구독 설정", "preferences_automatic_instance_redirect_label": "자동 인스턴스 리디렉션 (redirect.invidious.io로 대체): ", "preferences_thin_mode_label": "단순 모드: ", @@ -11,7 +11,7 @@ "preferences_dark_mode_label": "테마: ", "Dark mode: ": "다크 모드: ", "preferences_player_style_label": "플레이어 스타일: ", - "preferences_category_visual": "시각 설정", + "preferences_category_visual": "환경 설정", "preferences_vr_mode_label": "VR 영상 활성화(WebGL 필요): ", "preferences_extend_desc_label": "자동으로 비디오 설명을 확장: ", "preferences_annotations_label": "기본으로 주석 표시: ", @@ -26,7 +26,7 @@ "preferences_speed_label": "기본 속도: ", "preferences_local_label": "비디오를 프록시: ", "preferences_listen_label": "라디오 모드: ", - "preferences_continue_autoplay_label": "다음 동영상 자동재생 ", + "preferences_continue_autoplay_label": "다음 동영상 자동재생: ", "preferences_continue_label": "다음 동영상으로 이동: ", "preferences_autoplay_label": "자동재생: ", "preferences_video_loop_label": "항상 반복: ", @@ -37,8 +37,8 @@ "Register": "회원가입", "Sign In": "로그인", "preferences_category_misc": "기타 설정", - "Image CAPTCHA": "이미지 CAPTCHA", - "Text CAPTCHA": "텍스트 CAPTCHA", + "Image CAPTCHA": "이미지 캡차", + "Text CAPTCHA": "텍스트 캡차", "Time (h:mm:ss):": "시각 (h:mm:ss):", "Password": "비밀번호", "User ID": "사용자 ID", @@ -50,15 +50,15 @@ "An alternative front-end to YouTube": "유튜브의 프론트엔드 대안", "History": "역사", "Delete account?": "계정을 삭제 하시겠습니까?", - "Export data as JSON": "데이터를 JSON으로 내보내기", - "Export subscriptions as OPML (for NewPipe & FreeTube)": "구독을 OPML로 내보내기 (NewPipe 및 FreeTube 용)", - "Export subscriptions as OPML": "구독을 OPML로 내보내기", + "Export data as JSON": "JSON으로 데이터 내보내기", + "Export subscriptions as OPML (for NewPipe & FreeTube)": "OPML로 구독 내보내기 (뉴파이프 및 프리튜브)", + "Export subscriptions as OPML": "OPML로 구독 내보내기", "Export": "내보내기", - "Import NewPipe data (.zip)": "NewPipe 데이터 가져오기 (.zip)", - "Import NewPipe subscriptions (.json)": "NewPipe 구독을 가져오기 (.json)", - "Import FreeTube subscriptions (.db)": "FreeTube 구독 가져오기 (.db)", + "Import NewPipe data (.zip)": "뉴파이프 데이터 가져오기 (.zip)", + "Import NewPipe subscriptions (.json)": "뉴파이프 구독 가져오기 (.json)", + "Import FreeTube subscriptions (.db)": "프리튜브 구독 가져오기 (.db)", "Import YouTube subscriptions": "유튜브 구독 가져오기", - "Import Invidious data": "인비디어스 JSON 데이터 가져오기", + "Import Invidious data": "인비디어스 데이터 가져오기 (.json)", "Import": "가져오기", "Import and Export Data": "데이터 가져오기 및 내보내기", "No": "아니요", @@ -150,9 +150,9 @@ "Subscription manager": "구독 관리자", "Save preferences": "설정 저장", "Report statistics: ": "통계 보고: ", - "Registration enabled: ": "등록 활성화: ", + "Registration enabled: ": "회원가입 활성화: ", "Login enabled: ": "로그인 활성화: ", - "CAPTCHA enabled: ": "CAPTCHA 활성화: ", + "CAPTCHA enabled: ": "캡차 활성화: ", "Top enabled: ": "Top 활성화: ", "preferences_show_nick_label": "상단에 닉네임 표시: ", "preferences_feed_menu_label": "피드 메뉴: ", @@ -187,8 +187,8 @@ "Polish": "폴란드어", "Persian": "페르시아어", "Pashto": "파슈토어", - "Nyanja": "체와어", - "Norwegian Bokmål": "보크몰", + "Nyanja": "냔자어", + "Norwegian Bokmål": "노르웨이 부크몰어", "Nepali": "네팔어", "Mongolian": "몽골어", "Marathi": "마라티어", @@ -284,10 +284,10 @@ "Password cannot be empty": "비밀번호는 비워둘 수 없습니다", "Please sign in using 'Log in with Google'": "'구글로 로그인'을 사용하여 로그인하세요", "Wrong username or password": "잘못된 사용자 이름 또는 비밀번호", - "Password is a required field": "비밀번호는 필수 필드입니다", - "User ID is a required field": "사용자 ID는 필수 필드입니다", - "CAPTCHA is a required field": "CAPTCHA는 필수 필드입니다", - "Erroneous CAPTCHA": "잘못된 CAPTCHA", + "Password is a required field": "비밀번호는 필수 입력란입니다", + "User ID is a required field": "사용자 ID는 필수 입력란입니다", + "CAPTCHA is a required field": "캡차는 필수 입력란입니다", + "Erroneous CAPTCHA": "잘못된 캡차", "Login failed. This may be because two-factor authentication is not turned on for your account.": "로그인 실패. 계정에 이중 인증이 설정되어 있지 않기 때문일 수 있습니다.", "Blacklisted regions: ": "차단된 지역: ", "Playlists": "재생목록", @@ -297,7 +297,7 @@ "Empty playlist": "재생목록 비어 있음", "Show annotations": "주석 보이기", "Hide annotations": "주석 숨기기", - "Switch Invidious Instance": "Invidious 인스턴스 변경", + "Switch Invidious Instance": "인비디어스 인스턴스 변경", "Spanish": "스페인어", "Southern Sotho": "소토어", "Somali": "소말리어", @@ -347,8 +347,8 @@ "search_filters_sort_option_date": "업로드 날짜", "search_filters_sort_option_rating": "평점", "search_filters_sort_option_relevance": "관련성", - "Community": "커뮤니티", - "Videos": "동영상", + "channel_tab_community_label": "커뮤니티", + "channel_tab_videos_label": "동영상", "Video mode": "비디오 모드", "Audio mode": "오디오 모드", "permalink": "퍼머링크", @@ -383,7 +383,7 @@ "adminprefs_modified_source_code_url_label": "수정된 소스 코드 저장소의 URL", "search_filters_title": "필터", "preferences_quality_dash_option_4320p": "4320p", - "Popular enabled: ": "인기 급상승 활성화: ", + "Popular enabled: ": "인기 활성화: ", "Dutch (auto-generated)": "네덜란드어 (자동 생성됨)", "Chinese (Hong Kong)": "중국어 (홍콩)", "Chinese (Taiwan)": "중국어 (대만)", @@ -442,7 +442,7 @@ "preferences_save_player_pos_label": "이어서 보기: ", "none": "없음", "videoinfo_started_streaming_x_ago": "`x` 전에 스트리밍을 시작했습니다", - "crash_page_you_found_a_bug": "Invidious에서 버그를 찾은 것 같습니다!", + "crash_page_you_found_a_bug": "인비디어스에서 버그를 찾은 것 같습니다!", "download_subtitles": "자막 - `x`(.vtt)", "user_saved_playlists": "`x`개의 저장된 재생목록", "crash_page_before_reporting": "버그를 보고하기 전에 다음 사항이 있는지 확인합니다:", @@ -456,5 +456,9 @@ "crash_page_report_issue": "위의 방법 중 어느 것도 도움이 되지 않았다면, <a href=\"`x`\">깃허브에서 새 이슈를 열고</a>(가능하면 영어로) 메시지에 다음 텍스트를 포함하세요(해당 텍스트를 번역하지 마십시오):", "videoinfo_youTube_embed_link": "임베드", "videoinfo_invidious_embed_link": "임베드 링크", - "error_video_not_in_playlist": "요청한 동영상이 이 재생목록에 없습니다. <a href=\"`x`\">재생목록 목록을 보려면 여기를 클릭하십시오.</a>" + "error_video_not_in_playlist": "요청한 동영상이 이 재생목록에 없습니다. <a href=\"`x`\">재생목록 목록을 보려면 여기를 클릭하십시오.</a>", + "channel_tab_shorts_label": "쇼츠", + "channel_tab_streams_label": "실시간 스트리밍", + "channel_tab_channels_label": "채널", + "channel_tab_playlists_label": "재생목록" } diff --git a/locales/lt.json b/locales/lt.json index 35ababee..9bfcfdba 100644 --- a/locales/lt.json +++ b/locales/lt.json @@ -325,9 +325,9 @@ "`x` marked it with a ❤": "`x` pažymėjo tai su ❤", "Audio mode": "Garso rėžimas", "Video mode": "Vaizdo rėžimas", - "Videos": "Vaizdo įrašai", + "channel_tab_videos_label": "Vaizdo įrašai", "Playlists": "Grojaraiščiai", - "Community": "Bendruomenė", + "channel_tab_community_label": "Bendruomenė", "search_filters_sort_option_relevance": "Aktualumas", "search_filters_sort_option_rating": "Reitingas", "search_filters_sort_option_date": "Įkėlimo data", diff --git a/locales/nb-NO.json b/locales/nb-NO.json index f4c2021b..d29cca43 100644 --- a/locales/nb-NO.json +++ b/locales/nb-NO.json @@ -325,9 +325,9 @@ "`x` marked it with a ❤": "`x` levnet et ❤", "Audio mode": "Lydmodus", "Video mode": "Video-modus", - "Videos": "Videoer", + "channel_tab_videos_label": "Videoer", "Playlists": "Spillelister", - "Community": "Gemenskap", + "channel_tab_community_label": "Gemenskap", "search_filters_sort_option_relevance": "relevans", "search_filters_sort_option_rating": "vurdering", "search_filters_sort_option_date": "dato", diff --git a/locales/nl.json b/locales/nl.json index 17057553..dfc68671 100644 --- a/locales/nl.json +++ b/locales/nl.json @@ -320,9 +320,9 @@ "`x` marked it with a ❤": "`x` heeft dit gemarkeerd met ❤", "Audio mode": "Audiomodus", "Video mode": "Videomodus", - "Videos": "Video's", + "channel_tab_videos_label": "Video's", "Playlists": "Afspeellijsten", - "Community": "Gemeenschap", + "channel_tab_community_label": "Gemeenschap", "search_filters_sort_option_relevance": "relevantie", "search_filters_sort_option_rating": "beoordeling", "search_filters_sort_option_date": "datum", diff --git a/locales/pl.json b/locales/pl.json index f1a07490..b9c2a638 100644 --- a/locales/pl.json +++ b/locales/pl.json @@ -324,9 +324,9 @@ "`x` marked it with a ❤": "`x` oznaczonych ❤", "Audio mode": "Tryb audio", "Video mode": "Tryb wideo", - "Videos": "Filmy", + "channel_tab_videos_label": "Wideo", "Playlists": "Playlisty", - "Community": "Społeczność", + "channel_tab_community_label": "Społeczność", "search_filters_sort_option_relevance": "Trafność", "search_filters_sort_option_rating": "Ocena", "search_filters_sort_option_date": "Data przesłania", @@ -488,5 +488,9 @@ "search_message_use_another_instance": " Możesz także <a href=\"`x`\">wyszukać w innej instancji</a>.", "search_filters_type_option_all": "Dowolny typ", "search_filters_duration_option_none": "Dowolna długość", - "search_filters_duration_option_medium": "Średnia (4-20 minut)" + "search_filters_duration_option_medium": "Średnia (4-20 minut)", + "channel_tab_streams_label": "Na żywo", + "channel_tab_channels_label": "Kanały", + "channel_tab_playlists_label": "Playlisty", + "channel_tab_shorts_label": "Shorts" } diff --git a/locales/pt-BR.json b/locales/pt-BR.json index 41b457bb..112ed4b7 100644 --- a/locales/pt-BR.json +++ b/locales/pt-BR.json @@ -341,9 +341,9 @@ "`x` marked it with a ❤": "`x` foi marcado como ❤", "Audio mode": "Modo de áudio", "Video mode": "Modo de vídeo", - "Videos": "Vídeos", + "channel_tab_videos_label": "Vídeos", "Playlists": "Listas de reprodução", - "Community": "Comunidade", + "channel_tab_community_label": "Comunidade", "search_filters_sort_option_relevance": "relevância", "search_filters_sort_option_rating": "avaliação", "search_filters_sort_option_date": "data", diff --git a/locales/pt-PT.json b/locales/pt-PT.json index 1bee2807..1788deb1 100644 --- a/locales/pt-PT.json +++ b/locales/pt-PT.json @@ -341,9 +341,9 @@ "`x` marked it with a ❤": "`x` foi marcado como ❤", "Audio mode": "Modo de áudio", "Video mode": "Modo de vídeo", - "Videos": "Vídeos", + "channel_tab_videos_label": "Vídeos", "Playlists": "Listas de reprodução", - "Community": "Comunidade", + "channel_tab_community_label": "Comunidade", "search_filters_sort_option_relevance": "Relevância", "search_filters_sort_option_rating": "Avaliação", "search_filters_sort_option_date": "Data de envio", diff --git a/locales/pt.json b/locales/pt.json index b550bc87..2facba94 100644 --- a/locales/pt.json +++ b/locales/pt.json @@ -267,9 +267,9 @@ "Next page": "Próxima página", "last": "últimos", "Current version: ": "Versão atual: ", - "Community": "Comunidade", + "channel_tab_community_label": "Comunidade", "Playlists": "Listas de reprodução", - "Videos": "Vídeos", + "channel_tab_videos_label": "Vídeos", "Video mode": "Modo de vídeo", "Audio mode": "Modo de áudio", "`x` marked it with a ❤": "`x` foi marcado como ❤", diff --git a/locales/ro.json b/locales/ro.json index 342f5f37..0f6407d6 100644 --- a/locales/ro.json +++ b/locales/ro.json @@ -315,9 +315,9 @@ "`x` marked it with a ❤": "`x` l-a marcat cu o ❤", "Audio mode": "Mod audio", "Video mode": "Mod video", - "Videos": "Videoclipuri", + "channel_tab_videos_label": "Videoclipuri", "Playlists": "Liste de redare", - "Community": "Comunitate", + "channel_tab_community_label": "Comunitate", "Current version: ": "Versiunea actuală: ", "crash_page_read_the_faq": "citit lista <a href=\"`x`\">Întrebărilor Frecvente (FAQ)</a>", "generic_count_days_0": "{{count}} zi", diff --git a/locales/ru.json b/locales/ru.json index 93c9cbec..e54937a6 100644 --- a/locales/ru.json +++ b/locales/ru.json @@ -325,9 +325,9 @@ "`x` marked it with a ❤": "❤ от автора канала \"`x`\"", "Audio mode": "Аудио режим", "Video mode": "Видео режим", - "Videos": "Видео", + "channel_tab_videos_label": "Видео", "Playlists": "Плейлисты", - "Community": "Сообщество", + "channel_tab_community_label": "Сообщество", "search_filters_sort_option_relevance": "по актуальности", "search_filters_sort_option_rating": "по рейтингу", "search_filters_sort_option_date": "по дате загрузки", diff --git a/locales/sl.json b/locales/sl.json index 5994ca1a..f27bb20d 100644 --- a/locales/sl.json +++ b/locales/sl.json @@ -222,7 +222,7 @@ "About": "O aplikaciji", "%A %B %-d, %Y": "%A %-d %B %Y", "Audio mode": "Avdio način", - "Videos": "Videoposnetki", + "channel_tab_videos_label": "Videoposnetki", "search_filters_date_label": "Datum nalaganja", "search_filters_date_option_today": "Danes", "search_filters_date_option_week": "Ta teden", @@ -455,7 +455,7 @@ "Download": "Prenesi", "permalink": "stalna povezava", "`x` marked it with a ❤": "`x` ga je označil/a z ❤", - "Community": "Skupnost", + "channel_tab_community_label": "Skupnost", "search_filters_features_option_three_sixty": "360°", "Video mode": "Video način", "search_filters_features_option_c_commons": "Creative Commons", diff --git a/locales/sq.json b/locales/sq.json index 76dfd1b7..b8651316 100644 --- a/locales/sq.json +++ b/locales/sq.json @@ -259,10 +259,10 @@ "YouTube comment permalink": "Permalidhje komenti YouTube", "Audio mode": "Mënyrë për audion", "Playlists": "Luajlista", - "Community": "Bashkësi", + "channel_tab_community_label": "Bashkësi", "search_filters_sort_option_relevance": "Rëndësi", "Video mode": "Mënyrë video", - "Videos": "Video", + "channel_tab_videos_label": "Video", "search_filters_sort_option_rating": "Vlerësim", "search_filters_sort_option_date": "Datë ngarkimi", "search_filters_sort_option_views": "Numër parjesh", diff --git a/locales/sr.json b/locales/sr.json index d2f990ae..fd19c493 100644 --- a/locales/sr.json +++ b/locales/sr.json @@ -257,7 +257,7 @@ "preferences_volume_label": "Jačina zvuka: ", "preferences_locale_label": "Jezik: ", "adminprefs_modified_source_code_url_label": "URL veza do skladišta sa Izmenjenom Izvornom Kodom", - "Community": "Zajednica", + "channel_tab_community_label": "Zajednica", "Video mode": "Video mod", "Fallback captions: ": "Titl u slučaju da glavni nije dostupan: ", "Private": "Privatno", @@ -289,7 +289,7 @@ "Erroneous token": "Pogrešan žeton", "Czech": "Češki", "Latin": "Latinski", - "Videos": "Video klipovi", + "channel_tab_videos_label": "Video klipovi", "search_filters_features_option_four_k": "4К", "footer_donate_page": "Doniraj", "English": "Engleski", diff --git a/locales/sr_Cyrl.json b/locales/sr_Cyrl.json index c0f1224f..bef9915d 100644 --- a/locales/sr_Cyrl.json +++ b/locales/sr_Cyrl.json @@ -245,7 +245,7 @@ "(edited)": "(измењено)", "`x` marked it with a ❤": "`x` је означио/ла ово са ❤", "Audio mode": "Аудио мод", - "Videos": "Видео клипови", + "channel_tab_videos_label": "Видео клипови", "search_filters_sort_option_views": "Број прегледа", "search_filters_features_label": "Карактеристике", "search_filters_date_option_today": "Данас", @@ -298,7 +298,7 @@ "Ukrainian": "Украјински", "permalink": "трајна веза", "Pashto": "Паштунски", - "Community": "Заједница", + "channel_tab_community_label": "Заједница", "Sindhi": "Синди", "Could not fetch comments": "Узимање коментара није успело", "Bangla": "Бангла/Бенгалски", diff --git a/locales/sv-SE.json b/locales/sv-SE.json index 777899d0..39e94fd3 100644 --- a/locales/sv-SE.json +++ b/locales/sv-SE.json @@ -323,9 +323,9 @@ "`x` marked it with a ❤": "`x` lämnade ett ❤", "Audio mode": "Ljudläge", "Video mode": "Videoläge", - "Videos": "Videor", + "channel_tab_videos_label": "Videor", "Playlists": "Spellistor", - "Community": "Gemenskap", + "channel_tab_community_label": "Gemenskap", "search_filters_sort_option_relevance": "Relevans", "search_filters_sort_option_rating": "Rankning", "search_filters_sort_option_date": "Datum", diff --git a/locales/tr.json b/locales/tr.json index 17db1cf1..76cce15a 100644 --- a/locales/tr.json +++ b/locales/tr.json @@ -325,9 +325,9 @@ "`x` marked it with a ❤": "`x` ❤ İle İşaretledi", "Audio mode": "Ses Modu", "Video mode": "Video Modu", - "Videos": "Videolar", + "channel_tab_videos_label": "Videolar", "Playlists": "Oynatma Listeleri", - "Community": "Topluluk", + "channel_tab_community_label": "Topluluk", "search_filters_sort_option_relevance": "İlgi", "search_filters_sort_option_rating": "Değerlendirme", "search_filters_sort_option_date": "Yükleme Tarihi", @@ -472,5 +472,9 @@ "search_filters_title": "Filtreler", "search_message_change_filters_or_query": "Arama sorgunuzu genişletmeyi ve/veya filtreleri değiştirmeyi deneyin.", "Popular enabled: ": "Popüler Etkin: ", - "error_video_not_in_playlist": "İstenen video bu oynatma listesinde yok. <a href=\"`x`\">Oynatma listesi ana sayfası için buraya tıklayın.</a>" + "error_video_not_in_playlist": "İstenen video bu oynatma listesinde yok. <a href=\"`x`\">Oynatma listesi ana sayfası için buraya tıklayın.</a>", + "channel_tab_channels_label": "Kanallar", + "channel_tab_shorts_label": "Kısa Çekimler", + "channel_tab_streams_label": "Canlı Yayınlar", + "channel_tab_playlists_label": "Oynatma Listeleri" } diff --git a/locales/uk.json b/locales/uk.json index b6994c56..ae2fb5bd 100644 --- a/locales/uk.json +++ b/locales/uk.json @@ -54,7 +54,7 @@ "preferences_continue_label": "Завжди вмикати наступне відео: ", "preferences_continue_autoplay_label": "Автовідтворення наступного відео: ", "preferences_listen_label": "Режим «тільки звук» як усталений: ", - "preferences_local_label": "Програвати відео через проксі? ", + "preferences_local_label": "Відтворення відео через проксі: ", "preferences_speed_label": "Усталена швидкість відео: ", "preferences_quality_label": "Пріорітетна якість відео: ", "preferences_volume_label": "Гучність відео: ", @@ -63,13 +63,13 @@ "reddit": "Reddit", "preferences_captions_label": "Основна мова субтитрів: ", "Fallback captions: ": "Запасна мова субтитрів: ", - "preferences_related_videos_label": "Показувати схожі відео? ", - "preferences_annotations_label": "Завжди показувати анотації? ", + "preferences_related_videos_label": "Показувати схожі відео: ", + "preferences_annotations_label": "Завжди показувати анотації: ", "preferences_category_visual": "Налаштування сайту", "preferences_player_style_label": "Стиль програвача: ", - "Dark mode: ": "Темне оформлення: ", + "Dark mode: ": "Темний режим: ", "preferences_dark_mode_label": "Тема: ", - "dark": "темна", + "dark": "Темна", "light": "Світла", "preferences_thin_mode_label": "Полегшене оформлення: ", "preferences_category_subscription": "Налаштування підписок", @@ -101,11 +101,11 @@ "preferences_category_admin": "Адміністраторські налаштування", "preferences_default_home_label": "Усталена домашня сторінка: ", "preferences_feed_menu_label": "Меню потоку з відео: ", - "Top enabled: ": "Увімкнути топ відео? ", - "CAPTCHA enabled: ": "Увімкнути капчу? ", - "Login enabled: ": "Увімкнути авторизацію? ", - "Registration enabled: ": "Увімкнути реєстрацію? ", - "Report statistics: ": "Повідомляти статистику? ", + "Top enabled: ": "Увімкнути топ відео: ", + "CAPTCHA enabled: ": "Увімкнути CAPTCHA: ", + "Login enabled: ": "Увімкнути вхід: ", + "Registration enabled: ": "Увімкнути реєстрацію: ", + "Report statistics: ": "Повідомляти статистику: ", "Save preferences": "Зберегти налаштування", "Subscription manager": "Менеджер підписок", "Token manager": "Менеджер токенів", @@ -125,7 +125,7 @@ "Private": "Особистий", "View all playlists": "Переглянути всі списки відтворення", "Updated `x` ago": "Оновлено `x` тому", - "Delete playlist `x`?": "Видалити список відтворення \"x\"?", + "Delete playlist `x`?": "Видалити список відтворення `x`?", "Delete playlist": "Видалити список відтворення", "Create playlist": "Створити список відтворення", "Title": "Заголовок", @@ -315,9 +315,9 @@ "`x` marked it with a ❤": "❤ цьому від каналу `x`", "Audio mode": "Аудіорежим", "Video mode": "Відеорежим", - "Videos": "Відео", + "channel_tab_videos_label": "Відео", "Playlists": "Плейлисти", - "Community": "Спільнота", + "channel_tab_community_label": "Спільнота", "Current version: ": "Поточна версія: ", "generic_views_count_0": "{{count}} перегляд", "generic_views_count_1": "{{count}} перегляди", @@ -386,12 +386,12 @@ "Spanish (Mexico)": "Іспанська (Мексика)", "Spanish (Spain)": "Іспанська (Іспанія)", "next_steps_error_message_go_to_youtube": "Перейти до YouTube", - "footer_donate_page": "Пожертвувати", + "footer_donate_page": "Підтримати", "footer_documentation": "Документація", - "footer_source_code": "Вихідний код", - "footer_original_source_code": "Оригінал вихідного коду", - "footer_modfied_source_code": "Змінений вихідний код", - "adminprefs_modified_source_code_url_label": "URL-адреса репозиторію зміненого вихідного коду", + "footer_source_code": "Джерельний код", + "footer_original_source_code": "Оригінал джерельного коду", + "footer_modfied_source_code": "Змінений джерельний код", + "adminprefs_modified_source_code_url_label": "URL-адреса репозиторію зміненого джерельного коду", "none": "нема", "videoinfo_started_streaming_x_ago": "Трансляцію розпочато `x` тому", "crash_page_you_found_a_bug": "Схоже, ви знайшли ваду в Invidious!", @@ -408,7 +408,7 @@ "next_steps_error_message": "Після чого спробуйте: ", "next_steps_error_message_refresh": "Оновити сторінку", "Search": "Пошук", - "preferences_extend_desc_label": "Автоматично розширювати опис відео: ", + "preferences_extend_desc_label": "Автоматично розгортати опис відео: ", "preferences_category_misc": "Різноманітні параметри", "Show less": "Коротше", "preferences_quality_option_small": "Низька", @@ -488,5 +488,9 @@ "search_filters_sort_option_rating": "Рейтингові", "search_filters_sort_option_views": "Популярні", "Popular enabled: ": "Популярне ввімкнено: ", - "error_video_not_in_playlist": "Запитуваного відео в цьому списку відтворення не існує. <a href=\"`x`\">Клацніть тут, щоб переглянути домашню сторінку списку відтворення.</a>" + "error_video_not_in_playlist": "Запитуваного відео в цьому списку відтворення не існує. <a href=\"`x`\">Клацніть тут, щоб переглянути домашню сторінку списку відтворення.</a>", + "channel_tab_shorts_label": "Shorts", + "channel_tab_streams_label": "Прямі трансляції", + "channel_tab_playlists_label": "Добірки", + "channel_tab_channels_label": "Канали" } diff --git a/locales/vi.json b/locales/vi.json index 07fcf52f..3f7125c4 100644 --- a/locales/vi.json +++ b/locales/vi.json @@ -311,9 +311,9 @@ "`x` marked it with a ❤": "` x` đã đánh dấu nó bằng một ❤", "Audio mode": "Chế độ âm thanh", "Video mode": "Chế độ quay", - "Videos": "Video", + "channel_tab_videos_label": "Video", "Playlists": "Danh sách phát", - "Community": "Cộng đồng", + "channel_tab_community_label": "Cộng đồng", "search_filters_sort_option_relevance": "liên quan", "search_filters_sort_option_rating": "Xếp hạng", "search_filters_sort_option_date": "ngày", diff --git a/locales/zh-CN.json b/locales/zh-CN.json index 7e749dc9..385f16bd 100644 --- a/locales/zh-CN.json +++ b/locales/zh-CN.json @@ -341,9 +341,9 @@ "`x` marked it with a ❤": "`x` 为此加 ❤", "Audio mode": "音频模式", "Video mode": "视频模式", - "Videos": "视频", + "channel_tab_videos_label": "视频", "Playlists": "播放列表", - "Community": "社区", + "channel_tab_community_label": "社区", "search_filters_sort_option_relevance": "相关度", "search_filters_sort_option_rating": "评分", "search_filters_sort_option_date": "上传日期", diff --git a/locales/zh-TW.json b/locales/zh-TW.json index 54933701..3b51721d 100644 --- a/locales/zh-TW.json +++ b/locales/zh-TW.json @@ -341,9 +341,9 @@ "`x` marked it with a ❤": "`x` 為此標記 ❤", "Audio mode": "音訊模式", "Video mode": "視訊模式", - "Videos": "影片", + "channel_tab_videos_label": "影片", "Playlists": "播放清單", - "Community": "社群", + "channel_tab_community_label": "社群", "search_filters_sort_option_relevance": "關聯", "search_filters_sort_option_rating": "評分", "search_filters_sort_option_date": "日期", @@ -456,5 +456,9 @@ "search_filters_type_option_all": "任何類型", "search_filters_date_option_none": "任何日期", "Popular enabled: ": "已啟用人氣: ", - "error_video_not_in_playlist": "此播放清單不存在請求的影片。<a href=\"`x`\">點擊此處檢視播放清單首頁。</a>" + "error_video_not_in_playlist": "此播放清單不存在請求的影片。<a href=\"`x`\">點擊此處檢視播放清單首頁。</a>", + "channel_tab_shorts_label": "短片", + "channel_tab_playlists_label": "播放清單", + "channel_tab_channels_label": "頻道", + "channel_tab_streams_label": "直播" } diff --git a/scripts/deploy-database.sh b/scripts/deploy-database.sh index fa24b8f0..fa24b8f0 100644..100755 --- a/scripts/deploy-database.sh +++ b/scripts/deploy-database.sh diff --git a/scripts/fetch-player-dependencies.cr b/scripts/fetch-player-dependencies.cr index ed658b51..813e4ce4 100644..100755 --- a/scripts/fetch-player-dependencies.cr +++ b/scripts/fetch-player-dependencies.cr @@ -129,7 +129,7 @@ dependencies_to_install.each do |dep| dep = "videojs.markers" if dep == "videojs-markers" if File.exists?("#{download_path}/package/dist/#{dep}.css") - if minified && File.exists?("#{tmp_dir_path}/#{dep}/package/dist/#{dep}.min.css") + if minified && File.exists?("#{download_path}/package/dist/#{dep}.min.css") `mv #{download_path}/package/dist/#{dep}.min.css #{dest_path}/#{dep}.css` else `mv #{download_path}/package/dist/#{dep}.css #{dest_path}/#{dep}.css` diff --git a/scripts/install-dependencies.sh b/scripts/install-dependencies.sh index 1e67bdaf..1e67bdaf 100644..100755 --- a/scripts/install-dependencies.sh +++ b/scripts/install-dependencies.sh diff --git a/src/invidious.cr b/src/invidious.cr index 5064f0b8..d4f8e0fb 100644 --- a/src/invidious.cr +++ b/src/invidious.cr @@ -34,6 +34,7 @@ require "protodec/utils" require "./invidious/database/*" require "./invidious/database/migrations/*" +require "./invidious/http_server/*" require "./invidious/helpers/*" require "./invidious/yt_backend/*" require "./invidious/frontend/*" diff --git a/src/invidious/channels/community.cr b/src/invidious/channels/community.cr index 8e300288..76dff555 100644 --- a/src/invidious/channels/community.cr +++ b/src/invidious/channels/community.cr @@ -69,7 +69,7 @@ def fetch_channel_community(ucid, continuation, locale, format, thin_mode) next if !post content_html = post["contentText"]?.try { |t| parse_content(t) } || "" - author = post["authorText"]?.try &.["simpleText"]? || "" + author = post["authorText"]["runs"]?.try &.[0]?.try &.["text"]? || "" json.object do json.field "author", author diff --git a/src/invidious/helpers/json_filter.cr b/src/invidious/helpers/json_filter.cr index b8e8f96d..3f4080ba 100644 --- a/src/invidious/helpers/json_filter.cr +++ b/src/invidious/helpers/json_filter.cr @@ -20,7 +20,7 @@ module JSONFilter /^\(|\(\(|\/\(/ end - def self.parse_fields(fields_text : String) : Nil + def self.parse_fields(fields_text : String, &) : Nil if fields_text.empty? raise FieldsParser::ParseError.new "Fields is empty" end @@ -42,7 +42,7 @@ module JSONFilter parse_nest_groups(fields_text) { |nest_list| yield nest_list } end - def self.parse_single_nests(fields_text : String) : Nil + def self.parse_single_nests(fields_text : String, &) : Nil single_nests = remove_nest_groups(fields_text) if !single_nests.empty? @@ -60,7 +60,7 @@ module JSONFilter end end - def self.parse_nest_groups(fields_text : String) : Nil + 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) diff --git a/src/invidious/helpers/utils.cr b/src/invidious/helpers/utils.cr index ed0cca38..500a2582 100644 --- a/src/invidious/helpers/utils.cr +++ b/src/invidious/helpers/utils.cr @@ -162,7 +162,7 @@ def number_with_separator(number) end def short_text_to_number(short_text : String) : Int64 - matches = /(?<number>\d+(\.\d+)?)\s?(?<suffix>[mMkKbB])?/.match(short_text) + matches = /(?<number>\d+(\.\d+)?)\s?(?<suffix>[mMkKbB]?)/.match(short_text) number = matches.try &.["number"].to_f || 0.0 case matches.try &.["suffix"].downcase @@ -259,7 +259,7 @@ def get_referer(env, fallback = "/", unroll = true) end referer = referer.request_target - referer = "/" + referer.gsub(/[^\/?@&%=\-_.0-9a-zA-Z]/, "").lstrip("/\\") + referer = "/" + referer.gsub(/[^\/?@&%=\-_.:,0-9a-zA-Z]/, "").lstrip("/\\") if referer == env.request.path referer = fallback diff --git a/src/invidious/http_server/utils.cr b/src/invidious/http_server/utils.cr new file mode 100644 index 00000000..e3f1fa0f --- /dev/null +++ b/src/invidious/http_server/utils.cr @@ -0,0 +1,20 @@ +module Invidious::HttpServer + module Utils + extend self + + def proxy_video_url(raw_url : String, *, region : String? = nil, absolute : Bool = false) + url = URI.parse(raw_url) + + # Add some URL parameters + params = url.query_params + params["host"] = url.host.not_nil! # Should never be nil, in theory + params["region"] = region if !region.nil? + + if absolute + return "#{HOST_URL}#{url.request_target}?#{params}" + else + return "#{url.request_target}?#{params}" + end + end + end +end diff --git a/src/invidious/jsonify/api_v1/video_json.cr b/src/invidious/jsonify/api_v1/video_json.cr index 642789aa..a2b1a35c 100644 --- a/src/invidious/jsonify/api_v1/video_json.cr +++ b/src/invidious/jsonify/api_v1/video_json.cr @@ -3,7 +3,7 @@ require "json" module Invidious::JSONify::APIv1 extend self - def video(video : Video, json : JSON::Builder, *, locale : String?) + def video(video : Video, json : JSON::Builder, *, locale : String?, proxy : Bool = false) json.object do json.field "type", video.video_type @@ -89,7 +89,14 @@ module Invidious::JSONify::APIv1 # Not available on MPEG-4 Timed Text (`text/mp4`) streams (livestreams only) json.field "bitrate", fmt["bitrate"].as_i.to_s if fmt["bitrate"]? - json.field "url", fmt["url"] + if proxy + json.field "url", Invidious::HttpServer::Utils.proxy_video_url( + fmt["url"].to_s, absolute: true + ) + else + json.field "url", fmt["url"] + end + json.field "itag", fmt["itag"].as_i.to_s json.field "type", fmt["mimeType"] json.field "clen", fmt["contentLength"]? || "-1" diff --git a/src/invidious/routes/account.cr b/src/invidious/routes/account.cr index 9bb73136..e6a70ed2 100644 --- a/src/invidious/routes/account.cr +++ b/src/invidious/routes/account.cr @@ -203,7 +203,7 @@ module Invidious::Routes::Account referer = get_referer(env) if !user - return env.redirect referer + return env.redirect "/login?referer=#{URI.encode_path_segment(env.request.resource)}" end user = user.as(User) diff --git a/src/invidious/routes/api/manifest.cr b/src/invidious/routes/api/manifest.cr index ae65f10d..662d1002 100644 --- a/src/invidious/routes/api/manifest.cr +++ b/src/invidious/routes/api/manifest.cr @@ -29,7 +29,7 @@ module Invidious::Routes::API::Manifest if local uri = URI.parse(url) - url = "#{uri.request_target}host/#{uri.host}/" + url = "#{HOST_URL}#{uri.request_target}host/#{uri.host}/" end "<BaseURL>#{url}</BaseURL>" @@ -42,7 +42,7 @@ module Invidious::Routes::API::Manifest if local adaptive_fmts.each do |fmt| - fmt["url"] = JSON::Any.new(URI.parse(fmt["url"].as_s).request_target) + fmt["url"] = JSON::Any.new("#{HOST_URL}#{URI.parse(fmt["url"].as_s).request_target}") end end diff --git a/src/invidious/routes/api/v1/videos.cr b/src/invidious/routes/api/v1/videos.cr index a6b2eb4e..f312211e 100644 --- a/src/invidious/routes/api/v1/videos.cr +++ b/src/invidious/routes/api/v1/videos.cr @@ -6,6 +6,7 @@ module Invidious::Routes::API::V1::Videos id = env.params.url["id"] region = env.params.query["region"]? + proxy = {"1", "true"}.any? &.== env.params.query["local"]? begin video = get_video(id, region: region) @@ -15,7 +16,9 @@ module Invidious::Routes::API::V1::Videos return error_json(500, ex) end - video.to_json(locale, nil) + return JSON.build do |json| + Invidious::JSONify::APIv1.video(video, json, locale: locale, proxy: proxy) + end end def self.captions(env) @@ -90,45 +93,50 @@ module Invidious::Routes::API::V1::Videos # as well as some other markup that makes it cumbersome, so we try to fix that here if caption.name.includes? "auto-generated" caption_xml = YT_POOL.client &.get(url).body - caption_xml = XML.parse(caption_xml) - webvtt = String.build do |str| - str << <<-END_VTT - WEBVTT - Kind: captions - Language: #{tlang || caption.language_code} + if caption_xml.starts_with?("<?xml") + webvtt = caption.timedtext_to_vtt(caption_xml, tlang) + else + caption_xml = XML.parse(caption_xml) + webvtt = String.build do |str| + str << <<-END_VTT + WEBVTT + Kind: captions + Language: #{tlang || caption.language_code} - END_VTT - caption_nodes = caption_xml.xpath_nodes("//transcript/text") - caption_nodes.each_with_index do |node, i| - start_time = node["start"].to_f.seconds - duration = node["dur"]?.try &.to_f.seconds - duration ||= start_time + END_VTT - if caption_nodes.size > i + 1 - end_time = caption_nodes[i + 1]["start"].to_f.seconds - else - end_time = start_time + duration - end + caption_nodes = caption_xml.xpath_nodes("//transcript/text") + caption_nodes.each_with_index do |node, i| + start_time = node["start"].to_f.seconds + duration = node["dur"]?.try &.to_f.seconds + duration ||= start_time - start_time = "#{start_time.hours.to_s.rjust(2, '0')}:#{start_time.minutes.to_s.rjust(2, '0')}:#{start_time.seconds.to_s.rjust(2, '0')}.#{start_time.milliseconds.to_s.rjust(3, '0')}" - end_time = "#{end_time.hours.to_s.rjust(2, '0')}:#{end_time.minutes.to_s.rjust(2, '0')}:#{end_time.seconds.to_s.rjust(2, '0')}.#{end_time.milliseconds.to_s.rjust(3, '0')}" + if caption_nodes.size > i + 1 + end_time = caption_nodes[i + 1]["start"].to_f.seconds + else + end_time = start_time + duration + end - text = HTML.unescape(node.content) - text = text.gsub(/<font color="#[a-fA-F0-9]{6}">/, "") - text = text.gsub(/<\/font>/, "") - if md = text.match(/(?<name>.*) : (?<text>.*)/) - text = "<v #{md["name"]}>#{md["text"]}</v>" - end + start_time = "#{start_time.hours.to_s.rjust(2, '0')}:#{start_time.minutes.to_s.rjust(2, '0')}:#{start_time.seconds.to_s.rjust(2, '0')}.#{start_time.milliseconds.to_s.rjust(3, '0')}" + end_time = "#{end_time.hours.to_s.rjust(2, '0')}:#{end_time.minutes.to_s.rjust(2, '0')}:#{end_time.seconds.to_s.rjust(2, '0')}.#{end_time.milliseconds.to_s.rjust(3, '0')}" + + text = HTML.unescape(node.content) + text = text.gsub(/<font color="#[a-fA-F0-9]{6}">/, "") + text = text.gsub(/<\/font>/, "") + if md = text.match(/(?<name>.*) : (?<text>.*)/) + text = "<v #{md["name"]}>#{md["text"]}</v>" + end - str << <<-END_CUE - #{start_time} --> #{end_time} - #{text} + str << <<-END_CUE + #{start_time} --> #{end_time} + #{text} - END_CUE + END_CUE + end end end else @@ -138,7 +146,12 @@ module Invidious::Routes::API::V1::Videos # # See: https://github.com/iv-org/invidious/issues/2391 webvtt = YT_POOL.client &.get("#{url}&format=vtt").body - .gsub(/([0-9:.]{12} --> [0-9:.]{12}).+/, "\\1") + if webvtt.starts_with?("<?xml") + webvtt = caption.timedtext_to_vtt(webvtt) + else + webvtt = YT_POOL.client &.get("#{url}&format=vtt").body + .gsub(/([0-9:.]{12} --> [0-9:.]{12}).+/, "\\1") + end end if title = env.params.query["title"]? diff --git a/src/invidious/routes/login.cr b/src/invidious/routes/login.cr index 99fc13a2..6454131a 100644 --- a/src/invidious/routes/login.cr +++ b/src/invidious/routes/login.cr @@ -6,14 +6,14 @@ module Invidious::Routes::Login user = env.get? "user" - return env.redirect "/feed/subscriptions" if user + referer = get_referer(env, "/feed/subscriptions") + + return env.redirect referer if user if !CONFIG.login_enabled return error_template(400, "Login has been disabled by administrator.") end - referer = get_referer(env, "/feed/subscriptions") - email = nil password = nil captcha = nil diff --git a/src/invidious/routes/video_playback.cr b/src/invidious/routes/video_playback.cr index 560f9c19..1e932d11 100644 --- a/src/invidious/routes/video_playback.cr +++ b/src/invidious/routes/video_playback.cr @@ -35,6 +35,13 @@ module Invidious::Routes::VideoPlayback end end + # See: https://github.com/iv-org/invidious/issues/3302 + range_header = env.request.headers["Range"]? + if range_header.nil? + range_for_head = query_params["range"]? || "0-640" + headers["Range"] = "bytes=#{range_for_head}" + end + client = make_client(URI.parse(host), region) response = HTTP::Client::Response.new(500) error = "" @@ -70,6 +77,9 @@ module Invidious::Routes::VideoPlayback end end + # Remove the Range header added previously. + headers.delete("Range") if range_header.nil? + if response.status_code >= 400 env.response.content_type = "text/plain" haltf env, response.status_code @@ -91,14 +101,8 @@ module Invidious::Routes::VideoPlayback env.response.headers["Access-Control-Allow-Origin"] = "*" if location = resp.headers["Location"]? - location = URI.parse(location) - location = "#{location.request_target}&host=#{location.host}" - - if region - location += "®ion=#{region}" - end - - return env.redirect location + url = Invidious::HttpServer::Utils.proxy_video_url(location, region: region) + return env.redirect url end IO.copy(resp.body_io, env.response) diff --git a/src/invidious/routing.cr b/src/invidious/routing.cr index 491022a5..157e6de7 100644 --- a/src/invidious/routing.cr +++ b/src/invidious/routing.cr @@ -132,6 +132,8 @@ module Invidious::Routing get "/c/:user#{path}", Routes::Channels, :brand_redirect # /user/linustechtips | Not always the same as /c/ get "/user/:user#{path}", Routes::Channels, :brand_redirect + # /@LinusTechTips | Handle + get "/@:user#{path}", Routes::Channels, :brand_redirect # /attribution_link?a=anything&u=/channel/UCZYTClx2T1of7BRZ86-8fow get "/attribution_link#{path}", Routes::Channels, :brand_redirect # /profile?user=linustechtips diff --git a/src/invidious/videos/caption.cr b/src/invidious/videos/caption.cr index 4642c1a7..13f81a31 100644 --- a/src/invidious/videos/caption.cr +++ b/src/invidious/videos/caption.cr @@ -31,6 +31,72 @@ module Invidious::Videos return captions_list end + def timedtext_to_vtt(timedtext : String, tlang = nil) : String + # In the future, we could just directly work with the url. This is more of a POC + cues = [] of XML::Node + tree = XML.parse(timedtext) + tree = tree.children.first + + tree.children.each do |item| + if item.name == "body" + item.children.each do |cue| + if cue.name == "p" && !(cue.children.size == 1 && cue.children[0].content == "\n") + cues << cue + end + end + break + end + end + result = String.build do |result| + result << <<-END_VTT + WEBVTT + Kind: captions + Language: #{tlang || @language_code} + + + END_VTT + + result << "\n\n" + + cues.each_with_index do |node, i| + start_time = node["t"].to_f.milliseconds + + duration = node["d"]?.try &.to_f.milliseconds + + duration ||= start_time + + if cues.size > i + 1 + end_time = cues[i + 1]["t"].to_f.milliseconds + else + end_time = start_time + duration + end + + # start_time + result << start_time.hours.to_s.rjust(2, '0') + result << ':' << start_time.minutes.to_s.rjust(2, '0') + result << ':' << start_time.seconds.to_s.rjust(2, '0') + result << '.' << start_time.milliseconds.to_s.rjust(3, '0') + + result << " --> " + + # end_time + result << end_time.hours.to_s.rjust(2, '0') + result << ':' << end_time.minutes.to_s.rjust(2, '0') + result << ':' << end_time.seconds.to_s.rjust(2, '0') + result << '.' << end_time.milliseconds.to_s.rjust(3, '0') + + result << "\n" + + node.children.each do |s| + result << s.content + end + result << "\n" + result << "\n" + end + end + return result + end + # List of all caption languages available on Youtube. LANGUAGES = { "", diff --git a/src/invidious/videos/parser.cr b/src/invidious/videos/parser.cr index 0abac32f..cf43f1be 100644 --- a/src/invidious/videos/parser.cr +++ b/src/invidious/videos/parser.cr @@ -66,8 +66,10 @@ def extract_video_info(video_id : String, proxy_region : String? = nil) reason ||= subreason.try &.[]("runs").as_a.map(&.[]("text")).join("") reason ||= player_response.dig("playabilityStatus", "reason").as_s - # Stop here if video is not a scheduled livestream - if !{"LIVE_STREAM_OFFLINE", "LOGIN_REQUIRED"}.any?(playability_status) + # Stop here if video is not a scheduled livestream or + # for LOGIN_REQUIRED when videoDetails element is not found because retrying won't help + if !{"LIVE_STREAM_OFFLINE", "LOGIN_REQUIRED"}.any?(playability_status) || + playability_status == "LOGIN_REQUIRED" && !player_response.dig?("videoDetails") return { "version" => JSON::Any.new(Video::SCHEMA_VERSION.to_i64), "reason" => JSON::Any.new(reason), diff --git a/src/invidious/yt_backend/extractors.cr b/src/invidious/yt_backend/extractors.cr index 65d107b2..b14ad7b9 100644 --- a/src/invidious/yt_backend/extractors.cr +++ b/src/invidious/yt_backend/extractors.cr @@ -172,7 +172,17 @@ private module Parsers # When public subscriber count is disabled, the subscriberCountText isn't sent by InnerTube. # Always simpleText # TODO change default value to nil + subscriber_count = item_contents.dig?("subscriberCountText", "simpleText") + + # Since youtube added channel handles, `VideoCountText` holds the number of + # subscribers and `subscriberCountText` holds the handle, except when the + # channel doesn't have a handle (e.g: some topic music channels). + # See https://github.com/iv-org/invidious/issues/3394#issuecomment-1321261688 + if !subscriber_count || !subscriber_count.as_s.includes? " subscriber" + subscriber_count = item_contents.dig?("videoCountText", "simpleText") + end + subscriber_count = subscriber_count .try { |s| short_text_to_number(s.as_s.split(" ")[0]).to_i32 } || 0 # Auto-generated channels doesn't have videoCountText @@ -682,7 +692,11 @@ module HelperExtractors # Returns a 0 when it's unable to do so def self.get_video_count(container : JSON::Any) : Int32 if box = container["videoCountText"]? - return extract_text(box).try &.gsub(/\D/, "").to_i || 0 + if (extracted_text = extract_text(box)) && !extracted_text.includes? " subscriber" + return extracted_text.gsub(/\D/, "").to_i + else + return 0 + end elsif box = container["videoCount"]? return box.as_s.to_i else |
