diff --git a/README.md b/README.md index 7fd270048..e79faa7ea 100644 --- a/README.md +++ b/README.md @@ -31,7 +31,7 @@ • <a href="https://instances.invidious.io/">Instances list</a> • - <a href="https://docs.invidious.io/FAQ.md">FAQ</a> + <a href="https://docs.invidious.io/FAQ/">FAQ</a> • <a href="https://docs.invidious.io/">Documentation</a> • @@ -88,7 +88,7 @@ **Technical features** - Embedded video support -- [Developer API](https://docs.invidious.io/API.md) +- [Developer API](https://docs.invidious.io/API/) - Does not use official YouTube APIs - No Contributor License Agreement (CLA) @@ -101,7 +101,7 @@ **Hosting invidious:** -- [Follow the installation instructions](https://docs.invidious.io/Installation.md) +- [Follow the installation instructions](https://docs.invidious.io/Installation/) ## Documentation @@ -119,7 +119,7 @@ embedded youtube videos on other websites with invidious. The documentation contains a list of browser extensions that we recommended to use along with Invidious. -You can read more here: https://docs.invidious.io/Extensions.md +You can read more here: https://docs.invidious.io/Extensions/ ## Contribute diff --git a/kubernetes/Chart.lock b/kubernetes/Chart.lock index 1799798b7..37fcdbbd2 100644 --- a/kubernetes/Chart.lock +++ b/kubernetes/Chart.lock @@ -1,6 +1,6 @@ dependencies: - name: postgresql - repository: https://kubernetes-charts.storage.googleapis.com/ - version: 8.3.0 -digest: sha256:1feec3c396cbf27573dc201831ccd3376a4a6b58b2e7618ce30a89b8f5d707fd -generated: "2020-02-07T13:39:38.624846+01:00" + repository: https://charts.bitnami.com/bitnami/ + version: 11.1.3 +digest: sha256:79061645472b6fb342d45e8e5b3aacd018ef5067193e46a060bccdc99fe7f6e1 +generated: "2022-03-02T05:57:20.081432389+13:00" diff --git a/kubernetes/Chart.yaml b/kubernetes/Chart.yaml index 9e4b793e6..ca44f4b76 100644 --- a/kubernetes/Chart.yaml +++ b/kubernetes/Chart.yaml @@ -1,7 +1,7 @@ apiVersion: v2 name: invidious description: Invidious is an alternative front-end to YouTube -version: 1.1.0 +version: 1.1.1 appVersion: 0.20.1 keywords: - youtube @@ -17,6 +17,6 @@ maintainers: email: mail@leonklingele.de dependencies: - name: postgresql - version: ~8.3.0 - repository: "https://kubernetes-charts.storage.googleapis.com/" + version: ~11.1.3 + repository: "https://charts.bitnami.com/bitnami/" engine: gotpl diff --git a/kubernetes/values.yaml b/kubernetes/values.yaml index f241970c9..2dc4db2c4 100644 --- a/kubernetes/values.yaml +++ b/kubernetes/values.yaml @@ -14,7 +14,7 @@ autoscaling: targetCPUUtilizationPercentage: 50 service: - type: clusterIP + type: ClusterIP port: 3000 #loadBalancerIP: @@ -32,14 +32,19 @@ securityContext: runAsGroup: 1000 fsGroup: 1000 -# See https://github.com/helm/charts/tree/master/stable/postgresql +# See https://github.com/bitnami/charts/tree/master/bitnami/postgresql postgresql: - postgresqlUsername: kemal - postgresqlPassword: kemal - postgresqlDatabase: invidious - initdbUsername: kemal - initdbPassword: kemal - initdbScriptsConfigMap: invidious-postgresql-init + image: + registry: quay.io + auth: + username: kemal + password: kemal + database: invidious + primary: + initdb: + username: kemal + password: kemal + scriptsConfigMap: invidious-postgresql-init # Adapted from ../config/config.yml config: diff --git a/locales/ar.json b/locales/ar.json index 306566d75..10ef200b6 100644 --- a/locales/ar.json +++ b/locales/ar.json @@ -21,15 +21,15 @@ "No": "لا", "Import and Export Data": "اِستيراد البيانات وتصديرها", "Import": "استيراد", - "Import Invidious data": "استيراد بيانات انفيدياس", - "Import YouTube subscriptions": "استيراد اشتراكات يوتيوب", + "Import Invidious data": "استيراد بيانات JSON Invidious", + "Import YouTube subscriptions": "استيراد اشتراكات YouTube/OPML", "Import FreeTube subscriptions (.db)": "استيراد اشتراكات فريتيوب (.db)", "Import NewPipe subscriptions (.json)": "استيراد اشتراكات نيو بايب (.json)", "Import NewPipe data (.zip)": "استيراد بيانات نيو بايب (.zip)", "Export": "تصدير", "Export subscriptions as OPML": "تصدير الاشتراكات كـOPML", "Export subscriptions as OPML (for NewPipe & FreeTube)": "تصدير الاشتراكات كـOPML (لِنيو بايب و فريتيوب)", - "Export data as JSON": "تصدير البيانات بتنسيق JSON", + "Export data as JSON": "تصدير بيانات Invidious كـ JSON", "Delete account?": "حذف الحساب؟", "History": "السِّجل", "An alternative front-end to YouTube": "واجهة أمامية بديلة لموقع يوتيوب", @@ -66,7 +66,7 @@ "preferences_related_videos_label": "اعرض الفيديوهات ذات الصلة: ", "preferences_annotations_label": "اعرض الملاحظات في الفيديو تلقائيا: ", "preferences_extend_desc_label": "توسيع وصف الفيديو تلقائيا: ", - "preferences_vr_mode_label": "مقاطع فيديو تفاعلية ب درجة 360: ", + "preferences_vr_mode_label": "مقاطع فيديو تفاعلية بزاوية 360 درجة (تتطلب WebGL): ", "preferences_category_visual": "التفضيلات المرئية", "preferences_player_style_label": "شكل مشغل الفيديوهات: ", "Dark mode: ": "الوضع الليلي: ", @@ -108,9 +108,9 @@ "preferences_show_nick_label": "إظهار اللقب في الأعلى: ", "Top enabled: ": "تفعيل 'الأفضل' ؟ ", "CAPTCHA enabled: ": "تفعيل الكابتشا: ", - "Login enabled: ": "تفعيل الولوج: ", + "Login enabled: ": "تمكين تسجيل الدخول: ", "Registration enabled: ": "تفعيل التسجيل: ", - "Report statistics: ": "الإبلاغ عن الإحصائيات: ", + "Report statistics: ": "تقرير الإحصائيات: ", "Save preferences": "حفظ الإعدادات", "Subscription manager": "مدير الاشتراكات", "Token manager": "إداره الرمز", @@ -175,7 +175,7 @@ "User ID is a required field": "مكان اسم المستخدم مطلوب", "Password is a required field": "مكان كلمة السر مطلوب", "Wrong username or password": "اسم المستخدم او كلمة السر غير صحيح", - "Please sign in using 'Log in with Google'": "الرجاء تسجيل الدخول 'تسجيل الدخول بواسطة جوجل'", + "Please sign in using 'Log in with Google'": "الرجاء تسجيل الدخول باستخدام \"تسجيل الدخول باستخدام Google\"", "Password cannot be empty": "لا يمكن أن تكون كلمة السر فارغة", "Password cannot be longer than 55 characters": "يجب أن لا تتعدى كلمة السر 55 حرفًا", "Please log in": "الرجاء تسجيل الدخول", @@ -187,7 +187,7 @@ "Could not fetch comments": "لم يتمكن من إحضار التعليقات", "`x` ago": "`x` منذ", "Load more": "عرض المزيد", - "Could not create mix.": "لم يستطع عمل خلط.", + "Could not create mix.": "تعذر إنشاء مزيج.", "Empty playlist": "قائمة التشغيل فارغة", "Not a playlist.": "قائمة التشغيل غير صالحة.", "Playlist does not exist.": "قائمة التشغيل غير موجودة.", @@ -195,7 +195,7 @@ "Hidden field \"challenge\" is a required field": "مكان مخفي \"تحدي\" مكان مطلوب", "Hidden field \"token\" is a required field": "مكان مخفي \"رمز\" مكان مطلوب", "Erroneous challenge": "تحدي غير صالح", - "Erroneous token": "روز غير صالح", + "Erroneous token": "رمز مميز خاطئ", "No such user": "مستخدم غير صالح", "Token is expired, please try again": "الرمز منتهى الصلاحية، الرجاء المحاولة مرة اخرى", "English": "إنجليزي", @@ -337,7 +337,7 @@ "duration": "المدة الزمنية", "features": "الميزات", "sort": "فرز", - "hour": "ساعة", + "hour": "آخر ساعة", "today": "اليوم", "week": "هذا الأسبوع", "month": "هذا الشهر", @@ -363,7 +363,7 @@ "short": "قصير (< 4 دقائق)", "long": "طويل (> 20 دقيقة)", "footer_source_code": "شفرة المصدر", - "footer_original_source_code": "شفرة المصدر الأصلية", + "footer_original_source_code": "كود المصدر الأصلي", "footer_modfied_source_code": "شفرة المصدر المعدلة", "adminprefs_modified_source_code_url_label": "URL إلى مستودع التعليمات البرمجية المصدرية المعدلة", "footer_documentation": "التوثيق", @@ -398,7 +398,7 @@ "360": "360°", "download_subtitles": "ترجمات - 'x' (.vtt)", "invidious": "الخيالي", - "preferences_save_player_pos_label": "احفظ وقت الفيديو الحالي: ", + "preferences_save_player_pos_label": "حفظ موضع التشغيل: ", "crash_page_you_found_a_bug": "يبدو أنك قد وجدت خطأً برمجيًّا في Invidious!", "generic_videos_count_0": "لا فيديوهات", "generic_videos_count_1": "فيديو واحد", @@ -429,5 +429,35 @@ "generic_playlists_count_2": "قائمتا تشغيل", "generic_playlists_count_3": "{{count}} قوائم تشغيل", "generic_playlists_count_4": "{{count}} قائمة تشغيل", - "generic_playlists_count_5": "{{count}} قائمة تشغيل" + "generic_playlists_count_5": "{{count}} قائمة تشغيل", + "English (United States)": "الإنجليزية (الولايات المتحدة)", + "Indonesian (auto-generated)": "إندونيسي (مُنشأ تلقائيًا)", + "Interlingue": "إنترلينغوي", + "Italian (auto-generated)": "الإيطالية (مُنشأة تلقائيًا)", + "Spanish (auto-generated)": "الأسبانية (تم إنشاؤه تلقائيًا)", + "crash_page_before_reporting": "قبل الإبلاغ عن خطأ، تأكد من وجود:", + "French (auto-generated)": "الفرنسية (مُنشأة تلقائيًا)", + "Portuguese (auto-generated)": "البرتغالية (تم إنشاؤه تلقائيًا)", + "Turkish (auto-generated)": "التركية (تم إنشاؤها تلقائيًا)", + "crash_page_refresh": "حاول <a href=\"`x`\"> تحديث الصفحة </a>", + "crash_page_switch_instance": "حاول <a href=\"`x`\"> استخدام مثيل آخر </a>", + "Korean (auto-generated)": "كوري (تم إنشاؤه تلقائيًا)", + "Spanish (Mexico)": "الإسبانية (المكسيك)", + "Vietnamese (auto-generated)": "فيتنامي (تم إنشاؤه تلقائيًا)", + "crash_page_report_issue": "إذا لم يساعد أي مما سبق، يرجى فتح <a href=\"`x`\"> مشكلة جديدة على GitHub </a> (ويفضل أن يكون باللغة الإنجليزية) وتضمين النص التالي في رسالتك (لا تترجم هذا النص):", + "crash_page_read_the_faq": "قراءة <a href=\"`x`\"> الأسئلة المتكررة (الأسئلة الشائعة) </a>", + "preferences_watch_history_label": "تمكين سجل المشاهدة: ", + "English (United Kingdom)": "الإنجليزية (المملكة المتحدة)", + "Cantonese (Hong Kong)": "الكانتونية (هونغ كونغ)", + "Chinese": "الصينية", + "Chinese (China)": "الصينية (الصين)", + "Chinese (Hong Kong)": "الصينية (هونج كونج)", + "Chinese (Taiwan)": "الصينية (تايوان)", + "Dutch (auto-generated)": "هولندي (تم إنشاؤه تلقائيًا)", + "German (auto-generated)": "ألماني (تم إنشاؤه تلقائيًا)", + "Japanese (auto-generated)": "اليابانية (مُنشأة تلقائيًا)", + "Portuguese (Brazil)": "البرتغالية (البرازيل)", + "Russian (auto-generated)": "الروسية (منشأة تلقائيا)", + "Spanish (Spain)": "الإسبانية (إسبانيا)", + "crash_page_search_issue": "بحثت عن <a href=\"`x`\"> المشكلات الموجودة على Github </a>" } diff --git a/locales/cs.json b/locales/cs.json index 7dc24cbcb..10dd685ec 100644 --- a/locales/cs.json +++ b/locales/cs.json @@ -1,6 +1,6 @@ { "LIVE": "ŽIVĚ", - "Shared `x` ago": "Sdíleno před `x`", + "Shared `x` ago": "Zveřejněno před `x`", "Unsubscribe": "Odhlásit odběr", "Subscribe": "Odebírat", "View channel on YouTube": "Otevřít kanál na YouTube", @@ -19,17 +19,17 @@ "Authorize token for `x`?": "Autorizovat token pro `x`?", "Yes": "Ano", "No": "Ne", - "Import and Export Data": "Import a Export údajů", - "Import": "Inport", - "Import Invidious data": "Importovat údaje Invidious", - "Import YouTube subscriptions": "Importovat odběry z YouTube", + "Import and Export Data": "Import a export dat", + "Import": "Importovat", + "Import Invidious data": "Importovat JSON údaje Invidious", + "Import YouTube subscriptions": "Importovat odběry z YouTube/OPML", "Import FreeTube subscriptions (.db)": "Importovat odběry z FreeTube (.db)", "Import NewPipe subscriptions (.json)": "Importovat odběry z NewPipe (.json)", "Import NewPipe data (.zip)": "Importovat údeje z NewPipe (.zip)", "Export": "Exportovat", "Export subscriptions as OPML": "Exportovat odběry jako OPML", "Export subscriptions as OPML (for NewPipe & FreeTube)": "Exportovat údaje jako OPML (na NewPipe a FreeTube)", - "Export data as JSON": "Exportovat data jako JSON", + "Export data as JSON": "Exportovat data Invidious jako JSON", "Delete account?": "Smazat účet?", "History": "Historie", "An alternative front-end to YouTube": "Alternativní front-end pro YouTube", @@ -38,7 +38,7 @@ "Log in": "Přihlásit se", "Log in/register": "Přihlásit se/vytvořit účet", "Log in with Google": "Přihlásit se s Googlem", - "User ID": "Uživatelské IČ", + "User ID": "ID uživatele", "Password": "Heslo", "Time (h:mm:ss):": "Čas (h:mm:ss):", "Text CAPTCHA": "Textové CAPTCHA", @@ -51,16 +51,16 @@ "preferences_category_player": "Nastavení přehravače", "preferences_video_loop_label": "Vždy opakovat: ", "preferences_autoplay_label": "Automatické přehrávání: ", - "preferences_continue_label": "Přehrát další ve výchozím stavu: ", + "preferences_continue_label": "Automaticky přehrát další: ", "preferences_continue_autoplay_label": "Automaticky přehrát další video: ", "preferences_listen_label": "Poslouchat ve výchozím nastavení: ", "preferences_local_label": "Video přes proxy: ", - "preferences_speed_label": "Základní Rychlost: ", + "preferences_speed_label": "Výchozí rychlost: ", "preferences_quality_label": "Preferovaná kvalita videa: ", "preferences_volume_label": "Hlasitost přehrávače: ", "preferences_comments_label": "Předpřipravené komentáře: ", "youtube": "YouTube", - "reddit": "reddit", + "reddit": "Reddit", "preferences_captions_label": "Standartní Titulky: ", "Fallback captions: ": "Záložní titulky: ", "preferences_related_videos_label": "Zobrazit podobné videa: ", @@ -93,23 +93,23 @@ "`x` is live": "`x` je živě", "preferences_category_data": "Nastavení dat", "Clear watch history": "Smazat historii", - "Import/export data": "importovat/exportovat data", + "Import/export data": "Importovat/exportovat data", "Change password": "Změnit heslo", "Manage subscriptions": "Spravovat odebírané kanály", - "Manage tokens": "Spravovat klíče", - "Watch history": "Historie Sledování", - "Delete account": "Smazat Účet", + "Manage tokens": "Spravovat tokeny", + "Watch history": "Historie sledování", + "Delete account": "Smazat účet", "preferences_category_admin": "Administrátorská nastavení", "preferences_default_home_label": "Základní domovská stránka: ", "preferences_feed_menu_label": "Menu doporučených: ", - "CAPTCHA enabled: ": "CAPTCHA povolen: ", + "CAPTCHA enabled: ": "CAPTCHA povolena: ", "Login enabled: ": "Přihlášení povoleno: ", "Registration enabled: ": "Registrace povolena ", "Report statistics: ": "Oznámit statistiky: ", "Save preferences": "Uložit nastavení", - "Subscription manager": "Správa Odběrů", - "Token manager": "Správa klíčů", - "Token": "Klíč", + "Subscription manager": "Správa odběrů", + "Token manager": "Správa tokenů", + "Token": "Token", "Import/export": "Importovat/exportovat", "unsubscribe": "odhlásit odběr", "revoke": "vrátit zpět", @@ -118,10 +118,10 @@ "Log out": "Odhlásit se", "Source available here.": "Zdrojový kód dostupný zde.", "View JavaScript license information.": "Zobrazit informace o licenci JavaScript .", - "View privacy policy.": "Zobrazit Zásady ochrany osobních údajů.", + "View privacy policy.": "Zobrazit zásady ochrany osobních údajů.", "Trending": "Trendy", "Public": "Veřejné", - "Unlisted": "Nevypsáno", + "Unlisted": "Neveřejné", "Private": "Soukromé", "View all playlists": "Zobrazit všechny playlisty", "Updated `x` ago": "Aktualizováno před `x`", @@ -133,12 +133,12 @@ "Show more": "Zobrazit více", "Show less": "Zobrazit méně", "Watch on YouTube": "Sledovat na YouTube", - "Hide annotations": "Skrýt vysvětlivky", - "Show annotations": "Zobrazit vysvětlivky", + "Hide annotations": "Skrýt poznámky", + "Show annotations": "Zobrazit poznámky", "Genre: ": "Žánr: ", "License: ": "Licence: ", "Family friendly? ": "Vhodné pro děti? ", - "Engagement: ": "Závaznost: ", + "Engagement: ": "Zapojení: ", "English": "Angličtina", "English (auto-generated)": "Angličtina (automaticky generováno)", "Afrikaans": "Afrikánština", @@ -262,27 +262,220 @@ "Video mode": "Videový režim", "Videos": "Videa", "Community": "Komunita", - "rating": "hodnocení", - "date": "datum", - "views": "zhlédnutí", - "duration": "délka", - "hour": "hodina", - "today": "dnes", - "week": "týden", - "month": "měsíc", - "year": "rok", - "video": "video", - "channel": "kanál", - "playlist": "playlist", - "movie": "film", - "show": "zobrazit", + "rating": "Hodnocení", + "date": "Datum zveřejnění", + "views": "Počet zhlédnutí", + "duration": "Délka", + "hour": "Před hodinou", + "today": "Dnes", + "week": "Tento týden", + "month": "Tento měsíc", + "year": "Tento rok", + "video": "Video", + "channel": "Kanál", + "playlist": "Playlist", + "movie": "Film", + "show": "Show", "hd": "HD", - "subtitles": "titulky", + "subtitles": "Titulky", "creative_commons": "Creative Commons", "3d": "3D", - "live": "živě", - "4k": "4k", - "location": "umístění", + "live": "Živě", + "4k": "4K", + "location": "Umístění", "hdr": "HDR", - "filter": "filtr" + "filter": "Filtr", + "generic_count_days_0": "{{count}} den", + "generic_count_days_1": "{{count}} dny", + "generic_count_days_2": "{{count}} dní", + "generic_count_hours_0": "{{count}} hodina", + "generic_count_hours_1": "{{count}} hodiny", + "generic_count_hours_2": "{{count}} hodin", + "crash_page_refresh": "zkusili <a href=\"`x`\">obnovit stránku</a>", + "crash_page_switch_instance": "zkusili <a href=\"`x`\">použít jinou instanci</a>", + "preferences_vr_mode_label": "Interaktivní 360-stupňová videa (vyžaduje WebGL): ", + "English (United Kingdom)": "Angličtina (Spojené království)", + "Chinese (China)": "Čínština (Čína)", + "Chinese (Hong Kong)": "Čínština (Hong Kong)", + "Chinese (Taiwan)": "Čínština (Taiwan)", + "Portuguese (auto-generated)": "Portugalština (automaticky generováno)", + "Spanish (auto-generated)": "Španělština (automaticky generováno)", + "Spanish (Mexico)": "Španělština (Mexiko)", + "Spanish (Spain)": "Španělština (Španělsko)", + "generic_count_years_0": "{{count}} rok", + "generic_count_years_1": "{{count}} roky", + "generic_count_years_2": "{{count}} let", + "Fallback comments: ": "Záložní komentáře: ", + "Search": "Hledat", + "Top": "Nejlepší", + "Playlists": "Playlisty", + "videoinfo_started_streaming_x_ago": "Stream spuštěn před `x`", + "videoinfo_watch_on_youTube": "Sledovat na YouTube", + "videoinfo_youTube_embed_link": "Vložení", + "crash_page_read_the_faq": "si přečetli <a href=\"`x`\">často kladené otázky (FAQ)</a>", + "crash_page_before_reporting": "Před nahlášením chyby se ujistěte, že jste:", + "preferences_quality_option_hd720": "HD720", + "preferences_quality_option_dash": "DASH (adaptivní kvalita)", + "generic_views_count_0": "{{count}} zhlédnutí", + "generic_views_count_1": "{{count}} zhlédnutí", + "generic_views_count_2": "{{count}} zhlédnutí", + "generic_subscriptions_count_0": "{{count}} odběr", + "generic_subscriptions_count_1": "{{count}} odběry", + "generic_subscriptions_count_2": "{{count}} odběrů", + "preferences_quality_dash_option_4320p": "4320p", + "generic_videos_count_0": "{{count}} video", + "generic_videos_count_1": "{{count}} videa", + "generic_videos_count_2": "{{count}} videí", + "preferences_quality_option_small": "Nízká", + "preferences_quality_dash_option_2160p": "2160p", + "preferences_quality_dash_option_1080p": "1080p", + "preferences_quality_dash_option_720p": "720p", + "preferences_quality_dash_option_360p": "360p", + "preferences_quality_dash_option_144p": "144p", + "preferences_quality_option_medium": "Střední", + "preferences_quality_dash_option_1440p": "1440p", + "invidious": "Invidious", + "View more comments on Reddit": "Zobrazit více komentářů na Redditu", + "Invalid TFA code": "Nesprávný TFA kód", + "generic_playlists_count_0": "{{count}} playlist", + "generic_playlists_count_1": "{{count}} playlisty", + "generic_playlists_count_2": "{{count}} playlistů", + "generic_subscribers_count_0": "{{count}} odběratel", + "generic_subscribers_count_1": "{{count}} odběratelé", + "generic_subscribers_count_2": "{{count}} odběratelů", + "preferences_watch_history_label": "Povolit historii sledování: ", + "preferences_quality_dash_option_240p": "240p", + "preferences_region_label": "Země obsahu: ", + "subscriptions_unseen_notifs_count_0": "{{count}} nezobrazené oznámení", + "subscriptions_unseen_notifs_count_1": "{{count}} nezobrazená oznámení", + "subscriptions_unseen_notifs_count_2": "{{count}} nezobrazených oznámení", + "Show replies": "Zobrazit odpovědi", + "Quota exceeded, try again in a few hours": "Kvóta překročena, zkuste to znovu za pár hodin", + "Password cannot be longer than 55 characters": "Heslo nesmí být delší než 55 znaků", + "comments_view_x_replies_0": "Zobrazit {{count}} odpověď", + "comments_view_x_replies_1": "Zobrazit {{count}} odpovědi", + "comments_view_x_replies_2": "Zobrazit {{count}} odpovědí", + "comments_points_count_0": "{{count}} bod", + "comments_points_count_1": "{{count}} body", + "comments_points_count_2": "{{count}} bodů", + "German (auto-generated)": "Němčina (automaticky generováno)", + "Indonesian (auto-generated)": "Indonéština (automaticky generováno)", + "Interlingue": "Interlingue", + "Italian (auto-generated)": "Italština (automaticky generováno)", + "Japanese (auto-generated)": "Japonština (automaticky generováno)", + "Korean (auto-generated)": "Korejština (automaticky generováno)", + "Russian (auto-generated)": "Ruština (automaticky generováno)", + "generic_count_months_0": "{{count}} měsíc", + "generic_count_months_1": "{{count}} měsíce", + "generic_count_months_2": "{{count}} měsíců", + "generic_count_weeks_0": "{{count}} týden", + "generic_count_weeks_1": "{{count}} týdny", + "generic_count_weeks_2": "{{count}} týdnů", + "generic_count_minutes_0": "{{count}} minuta", + "generic_count_minutes_1": "{{count}} minuty", + "generic_count_minutes_2": "{{count}} minut", + "short": "Krátké (< 4 minuty)", + "long": "Dlouhé (> 20 minut)", + "footer_documentation": "Dokumentace", + "next_steps_error_message_refresh": "Obnovit stránku", + "Chinese": "Čínština", + "360": "360°", + "Dutch (auto-generated)": "Nizozemština (automaticky generováno)", + "Erroneous token": "Chybný token", + "tokens_count_0": "{{count}} token", + "tokens_count_1": "{{count}} tokeny", + "tokens_count_2": "{{count}} tokenů", + "Portuguese (Brazil)": "Portugalština (Brazílie)", + "content_type": "Typ", + "sort": "Řazení", + "Token is expired, please try again": "Token vypršel, zkuste to prosím znovu", + "English (United States)": "Angličtina (Spojené státy)", + "Cantonese (Hong Kong)": "Kantonština (Hong Kong)", + "French (auto-generated)": "Francouzština (automaticky generováno)", + "Turkish (auto-generated)": "Turečtina (automaticky generováno)", + "Vietnamese (auto-generated)": "Vietnamština (automaticky generováno)", + "Current version: ": "Aktuální verze: ", + "next_steps_error_message": "Měli byste zkusit: ", + "footer_donate_page": "Přispět", + "download_subtitles": "Titulky - `x` (.vtt)", + "%A %B %-d, %Y": "%A %B %-d, %Y", + "YouTube comment permalink": "Permanentní odkaz YouTube komentáře", + "permalink": "permalink", + "purchased": "Zakoupeno", + "footer_original_source_code": "Původní zdrojový kód", + "adminprefs_modified_source_code_url_label": "URL repozitáře s upraveným zdrojovým kódem", + "Video unavailable": "Video není dostupné", + "next_steps_error_message_go_to_youtube": "Jít na YouTube", + "footer_modfied_source_code": "Upravený zdrojový kód", + "none": "žádné", + "videoinfo_invidious_embed_link": "Odkaz na vložení", + "user_saved_playlists": "`x` uložených playlistů", + "crash_page_you_found_a_bug": "Vypadá to, že jste našli chybu v Invidious!", + "user_created_playlists": "`x` vytvořených playlistů", + "crash_page_search_issue": "vyhledali <a href=\"`x`\">existující problémy na GitHubu</a>", + "crash_page_report_issue": "Pokud nepomohlo nic z výše uvedeného, <a href=\"`x`\">otevřete prosím nový problém na GitHubu</a> (pokud možno v angličtině) a zahrňte do zprávy následující text (NEpřekládejte jej):", + "preferences_quality_dash_label": "Preferovaná kvalita videí DASH: ", + "preferences_quality_dash_option_auto": "Automatická", + "preferences_quality_dash_option_best": "Nejlepší", + "preferences_quality_dash_option_worst": "Nejhorší", + "preferences_quality_dash_option_480p": "480p", + "Top enabled: ": "Povoleny nejlepší: ", + "generic_count_seconds_0": "{{count}} sekunda", + "generic_count_seconds_1": "{{count}} sekundy", + "generic_count_seconds_2": "{{count}} sekund", + "preferences_save_player_pos_label": "Uložit pozici přehrávání: ", + "Incorrect password": "Nesprávné heslo", + "View as playlist": "Zobrazit jako playlist", + "View Reddit comments": "Zobrazit komentáře z Redditu", + "No such user": "Uživatel nenalezen", + "Playlist privacy": "Soukromí playlistu", + "Wrong answer": "Špatná odpověď", + "Could not pull trending pages.": "Nepodařilo se získat trendy stránky.", + "Erroneous CAPTCHA": "Chybná CAPTCHA", + "Password is a required field": "Heslo je vyžadované pole", + "preferences_automatic_instance_redirect_label": "Automatické přesměrování instance (fallback na redirect.invidious.io): ", + "Broken? Try another Invidious Instance": "Je něco rozbité? Zkuste jinou instanci Invidious", + "Switch Invidious Instance": "Přepnout instanci Invidious", + "Empty playlist": "Prázdný playlist", + "footer_source_code": "Zdrojový kód", + "relevance": "Relevantnost", + "View YouTube comments": "Zobrazit YouTube komentáře", + "Blacklisted regions: ": "Oblasti na černé listině: ", + "Wrong username or password": "Nesprávné uživatelské jméno nebo heslo", + "Please sign in using 'Log in with Google'": "Přihlaste se prosím pomocí Googlu", + "Password cannot be empty": "Heslo nemůže být prázné", + "preferences_category_misc": "Různá nastavení", + "preferences_show_nick_label": "Zobrazit přezdívku na vrchu: ", + "Whitelisted regions: ": "Oblasti na bílé listině: ", + "Hi! Looks like you have JavaScript turned off. Click here to view comments, keep in mind they may take a bit longer to load.": "Zdravíme! Zdá se, že máte vypnutý JavaScript. Klikněte sem pro zobrazení komentářů - nezapomeňte, že se mohou načítat trochu déle.", + "User ID is a required field": "ID uživatele je vyžadované pole", + "Please log in": "Přihlaste se prosím", + "Invidious Private Feed for `x`": "Soukromý kanál Invidious pro `x`", + "Deleted or invalid channel": "Smazaný nebo neplatný kanál", + "This channel does not exist.": "Tento kanál neexistuje.", + "Hidden field \"token\" is a required field": "Skryté pole \"token\" je vyžadované", + "features": "Funkce", + "Wilson score: ": "Skóre Wilson: ", + "Shared `x`": "Sdíleno `x`", + "Premieres in `x`": "Premiéra za `x`", + "View `x` comments": { + "([^.,0-9]|^)1([^.,0-9]|$)": "Zobrazit `x` komentář", + "": "Zobrazit `x` komentářů" + }, + "Unable to log in, make sure two-factor authentication (Authenticator or SMS) is turned on.": "Nepodařilo se přihlásit, ujistěte se, že je povoleno dvoufázové ověřování (autentifikátor nebo SMS).", + "Login failed. This may be because two-factor authentication is not turned on for your account.": "Přihlášení selhalo. Toto se může stát, když není na vašem účtu povolené dvoufázové ověřování.", + "Could not get channel info.": "Nepodařilo se získat informace o kanálu.", + "Could not fetch comments": "Nepodařilo se získat komentáře", + "Could not create mix.": "Nepodařilo se vytvořit mix.", + "Hidden field \"challenge\" is a required field": "Skryté pole \"challenge\" je vyžadované", + "Released under the AGPLv3 on Github.": "Vydáno pod licencí AGPLv3 na GitHubu.", + "Hide replies": "Skrýt odpovědi", + "channel:`x`": "kanál: `x`", + "Load more": "Načíst další", + "Not a playlist.": "Není playlist.", + "Playlist does not exist.": "Playlist neexistuje.", + "Erroneous challenge": "Chybná výzva", + "Premieres `x`": "Premiéra `x`", + "CAPTCHA is a required field": "CAPTCHA je vyžadované pole", + "`x` ago": "Před `x`" } diff --git a/locales/de.json b/locales/de.json index 8381016bd..665810a43 100644 --- a/locales/de.json +++ b/locales/de.json @@ -141,7 +141,7 @@ "Show less": "Weniger anzeigen", "Watch on YouTube": "Video auf YouTube ansehen", "Switch Invidious Instance": "Invidious Instanz wechseln", - "Broken? Try another Invidious Instance": "Funktioniert nicht? Probiere eine andere Invidious Instanz aus", + "Broken? Try another Invidious Instance": "Kaputt? Versuche eine andere Invidious Instanz", "Hide annotations": "Anmerkungen ausblenden", "Show annotations": "Anmerkungen anzeigen", "Genre: ": "Genre: ", @@ -346,7 +346,7 @@ "channel": "Kanal", "playlist": "Wiedergabeliste", "movie": "Film", - "show": "Anzeigen", + "show": "anzeigen", "hd": "HD", "subtitles": "Untertitel / CC", "creative_commons": "Creative Commons", @@ -388,7 +388,7 @@ "Video unavailable": "Video nicht verfügbar", "user_created_playlists": "`x` Wiedergabelisten erstellt", "user_saved_playlists": "`x` Wiedergabelisten gespeichert", - "preferences_save_player_pos_label": "Aktuelle Position speichern: ", + "preferences_save_player_pos_label": "Aktuelle Position im Video speichern: ", "360": "360°", "preferences_quality_dash_option_best": "Höchste", "preferences_quality_dash_option_worst": "Niedrigste", @@ -398,5 +398,42 @@ "none": "keine", "videoinfo_started_streaming_x_ago": "Stream begann vor `x`", "videoinfo_watch_on_youTube": "Auf YouTube ansehen", - "preferences_quality_dash_label": "Bevorzugte DASH-Videoqualität: " + "preferences_quality_dash_label": "Bevorzugte DASH-Videoqualität: ", + "generic_subscribers_count": "{{count}} Abonnent", + "generic_subscribers_count_plural": "{{count}} Abonnenten", + "generic_videos_count": "{{count}} Video", + "generic_videos_count_plural": "{{count}} Videos", + "subscriptions_unseen_notifs_count": "{{count}} ungesehene Benachrichtung", + "subscriptions_unseen_notifs_count_plural": "{{count}} ungesehene Benachrichtungen", + "crash_page_refresh": "Versucht haben, <a href=\"`x`\">die Seite neu zu laden</a>", + "comments_view_x_replies": "{{count}} Antwort anzeigen", + "comments_view_x_replies_plural": "{{count}} Antworten anzeigen", + "generic_count_years": "{{count}} Jahr", + "generic_count_years_plural": "{{count}} Jahre", + "generic_count_weeks": "{{count}} Woche", + "generic_count_weeks_plural": "{{count}} Wochen", + "generic_count_days": "{{count}} Tag", + "generic_count_days_plural": "{{count}} Tage", + "crash_page_before_reporting": "Bevor Sie einen Bug melden, stellen Sie sicher, dass Sie:", + "crash_page_switch_instance": "Eine <a href=\"`x`\">andere Instanz</a> versucht haben", + "generic_count_hours": "{{count}} Stunde", + "generic_count_hours_plural": "{{count}} Stunden", + "generic_count_minutes": "{{count}} Minute", + "generic_count_minutes_plural": "{{count}} Minuten", + "crash_page_read_the_faq": "Das <a href=\"`x`\">FAQ</a> gelesen haben", + "crash_page_search_issue": "Nach <a href=\"`x`\">bereits gemeldeten Bugs auf Github</a> gesucht haben", + "crash_page_report_issue": "Wenn all dies nicht geholfen hat, <a href=\"`x`\">öffnen Sie bitte ein neues Problem (issue) auf Github</a> (vorzugsweise auf Englisch) und fügen Sie den folgenden Text in Ihre Nachricht ein (bitte übersetzen Sie diesen Text NICHT):", + "generic_views_count": "{{count}} Aufruf", + "generic_views_count_plural": "{{count}} Aufrufe", + "generic_count_seconds": "{{count}} Sekunde", + "generic_count_seconds_plural": "{{count}} Sekunden", + "generic_subscriptions_count": "{{count}} Abo", + "generic_subscriptions_count_plural": "{{count}} Abos", + "tokens_count": "{{count}} Token", + "tokens_count_plural": "{{count}} Tokens", + "comments_points_count": "{{count}} Punkt", + "comments_points_count_plural": "{{count}} Punkte", + "crash_page_you_found_a_bug": "Anscheinend haben Sie einen Fehler in Invidious gefunden!", + "generic_count_months": "{{count}} Monat", + "generic_count_months_plural": "{{count}} Monate" } diff --git a/locales/el.json b/locales/el.json index 36fc695b2..24e421538 100644 --- a/locales/el.json +++ b/locales/el.json @@ -448,5 +448,6 @@ "none": "κανένα", "videoinfo_youTube_embed_link": "Ενσωμάτωση", "videoinfo_invidious_embed_link": "Σύνδεσμος Ενσωμάτωσης", - "show": "Μπάρα προόδου διαβάσματος" + "show": "Μπάρα προόδου διαβάσματος", + "preferences_watch_history_label": "Ενεργοποίηση ιστορικού παρακολούθησης: " } diff --git a/locales/en-US.json b/locales/en-US.json index 1335d3847..a78d8062a 100644 --- a/locales/en-US.json +++ b/locales/en-US.json @@ -459,7 +459,7 @@ "crash_page_before_reporting": "Before reporting a bug, make sure that you have:", "crash_page_refresh": "tried to <a href=\"`x`\">refresh the page</a>", "crash_page_switch_instance": "tried to <a href=\"`x`\">use another instance</a>", - "crash_page_read_the_faq": "read the <a href=\"`x`\">Frenquently Asked Questions (FAQ)</a>", + "crash_page_read_the_faq": "read the <a href=\"`x`\">Frequently Asked Questions (FAQ)</a>", "crash_page_search_issue": "searched for <a href=\"`x`\">existing issues on Github</a>", "crash_page_report_issue": "If none of the above helped, please <a href=\"`x`\">open a new issue on GitHub</a> (preferably in English) and include the following text in your message (do NOT translate that text):" } diff --git a/locales/es.json b/locales/es.json index fbdb13ac4..689cb3104 100644 --- a/locales/es.json +++ b/locales/es.json @@ -196,7 +196,7 @@ "Hidden field \"token\" is a required field": "El campo oculto «símbolo» es un campo obligatorio", "Erroneous challenge": "Desafío no válido", "Erroneous token": "Símbolo no válido", - "No such user": "Usuario no válido", + "No such user": "Usuario no existe", "Token is expired, please try again": "El símbolo ha caducado, inténtelo de nuevo", "English": "Inglés", "English (auto-generated)": "Inglés (generados automáticamente)", @@ -358,7 +358,7 @@ "filter": "filtro", "Current version: ": "Versión actual: ", "next_steps_error_message": "Después de lo cual deberías intentar: ", - "next_steps_error_message_refresh": "Recargar", + "next_steps_error_message_refresh": "Recargar la página", "next_steps_error_message_go_to_youtube": "Ir a YouTube", "short": "Corto (< 4 minutos)", "long": "Largo (> 20 minutos)", diff --git a/locales/fi.json b/locales/fi.json index 7f97e8693..84090c242 100644 --- a/locales/fi.json +++ b/locales/fi.json @@ -21,15 +21,15 @@ "No": "Ei", "Import and Export Data": "Tuo ja vie tietoja", "Import": "Tuo", - "Import Invidious data": "Tuo Invidious-tietoja", - "Import YouTube subscriptions": "Tuo YouTube-tilaukset", + "Import Invidious data": "Tuo Invidiousin JSON-tietoja", + "Import YouTube subscriptions": "Tuo YouTube/OPML-tilaukset", "Import FreeTube subscriptions (.db)": "Tuo FreeTube-tilaukset (.db)", "Import NewPipe subscriptions (.json)": "Tuo NewPipe-tilaukset (.json)", "Import NewPipe data (.zip)": "Tuo NewPipe-tietoja (.zip)", "Export": "Vie", "Export subscriptions as OPML": "Vie tilaukset OPML-muodossa", "Export subscriptions as OPML (for NewPipe & FreeTube)": "Vie tilaukset OPML-muodossa (NewPipe & FreeTube)", - "Export data as JSON": "Vie data JSON-muodossa", + "Export data as JSON": "Vie Invidious-data JSON-muodossa", "Delete account?": "Poista tili?", "History": "Historia", "An alternative front-end to YouTube": "Vaihtoehtoinen front-end YouTubelle", @@ -66,7 +66,7 @@ "preferences_related_videos_label": "Näytä aiheeseen liittyviä videoita: ", "preferences_annotations_label": "Näytä huomautukset oletuksena: ", "preferences_extend_desc_label": "Laajenna automaattisesti videon kuvausta: ", - "preferences_vr_mode_label": "Interaktiiviset 360-asteiset videot: ", + "preferences_vr_mode_label": "Interaktiiviset 360-asteiset videot (vaatii WebGL:n): ", "preferences_category_visual": "Visuaaliset asetukset", "preferences_player_style_label": "Soittimen tyyli: ", "Dark mode: ": "Tumma tila: ", @@ -437,5 +437,29 @@ "long": "Pitkä (> 20 minuuttia)", "footer_documentation": "Dokumentaatio", "footer_original_source_code": "Alkuperäinen lähdekoodi", - "footer_modfied_source_code": "Muokattu lähdekoodi" + "footer_modfied_source_code": "Muokattu lähdekoodi", + "Japanese (auto-generated)": "Japani (automaattisesti luotu)", + "German (auto-generated)": "Saksa (automaattisesti luotu)", + "Portuguese (auto-generated)": "Portugali (automaattisesti luotu)", + "Russian (auto-generated)": "Venäjä (automaattisesti luotu)", + "preferences_watch_history_label": "Ota katseluhistoria käyttöön: ", + "English (United Kingdom)": "Englanti (Iso-Britannia)", + "English (United States)": "Englanti (Yhdysvallat)", + "Cantonese (Hong Kong)": "Kantoninkiina (Hong Kong)", + "Chinese": "Kiina", + "Chinese (China)": "Kiina (Kiina)", + "Chinese (Hong Kong)": "Kiina (Hong Kong)", + "Chinese (Taiwan)": "Kiina (Taiwan)", + "Dutch (auto-generated)": "Hollanti (automaattisesti luotu)", + "French (auto-generated)": "Ranska (automaattisesti luotu)", + "Indonesian (auto-generated)": "Indonesia (automaattisesti luotu)", + "Interlingue": "Interlingue", + "Italian (auto-generated)": "Italia (automaattisesti luotu)", + "Korean (auto-generated)": "Korea (automaattisesti luotu)", + "Portuguese (Brazil)": "Portugali (Brasilia)", + "Spanish (auto-generated)": "Espanja (automaattisesti luotu)", + "Spanish (Mexico)": "Espanja (Meksiko)", + "Spanish (Spain)": "Espanja (Espanja)", + "Turkish (auto-generated)": "Turkki (automaattisesti luotu)", + "Vietnamese (auto-generated)": "Vietnam (automaattisesti luotu)" } diff --git a/locales/hr.json b/locales/hr.json index 1de3fa795..688368d2f 100644 --- a/locales/hr.json +++ b/locales/hr.json @@ -88,7 +88,7 @@ "channel name": "ime kanala", "channel name - reverse": "ime kanala – obrnuto", "Only show latest video from channel: ": "Prikaži samo najnovija videa kanala: ", - "Only show latest unwatched video from channel: ": "Prikaži samo najnovija nepogledana videa kanala: ", + "Only show latest unwatched video from channel: ": "Prikaži samo najnovija nepogledana videa od kanala: ", "preferences_unseen_only_label": "Prikaži samo nepogledane: ", "preferences_notifications_only_label": "Prikaži samo obavijesti (ako ih ima): ", "Enable web notifications": "Aktiviraj web-obavijesti", @@ -476,5 +476,6 @@ "Chinese (Hong Kong)": "Kineski (Hong Kong)", "Korean (auto-generated)": "Korejski (automatski generiran)", "Portuguese (auto-generated)": "Portugalski (automatski generiran)", - "Spanish (auto-generated)": "Španjolski (automatski generiran)" + "Spanish (auto-generated)": "Španjolski (automatski generiran)", + "preferences_watch_history_label": "Aktiviraj povijest gledanja: " } diff --git a/locales/it.json b/locales/it.json index c80f4d96c..411148c90 100644 --- a/locales/it.json +++ b/locales/it.json @@ -390,7 +390,41 @@ "preferences_quality_dash_option_best": "Migliore", "preferences_quality_dash_option_worst": "Peggiore", "invidious": "Invidious", - "preferences_quality_dash_label": "Qualità video DASH preferita ", + "preferences_quality_dash_label": "Qualità video DASH preferita: ", "preferences_quality_option_hd720": "HD720", - "preferences_quality_dash_option_auto": "Automatica" + "preferences_quality_dash_option_auto": "Automatica", + "videoinfo_watch_on_youTube": "Guarda su YouTube", + "preferences_extend_desc_label": "Espandi automaticamente la descrizione del video: ", + "preferences_vr_mode_label": "Video interattivi a 360 gradi: ", + "Show less": "Mostra di meno", + "Switch Invidious Instance": "Cambia istanza Invidious", + "next_steps_error_message_go_to_youtube": "Andare su YouTube", + "footer_documentation": "Documentazione", + "footer_original_source_code": "Codice sorgente originale", + "footer_modfied_source_code": "Codice sorgente modificato", + "none": "nessuno", + "videoinfo_started_streaming_x_ago": "Ha iniziato a trasmettere `x` fa", + "download_subtitles": "Sottotitoli - `x` (.vtt)", + "user_saved_playlists": "playlist salvate da `x`", + "preferences_automatic_instance_redirect_label": "Reindirizzamento automatico dell'istanza (ripiego su redirect.invidious.io): ", + "Video unavailable": "Video non disponibile", + "preferences_show_nick_label": "Mostra nickname in alto: ", + "short": "Corto (< 4 minuti)", + "videoinfo_youTube_embed_link": "Incorpora", + "videoinfo_invidious_embed_link": "Incorpora collegamento", + "user_created_playlists": "playlist create da `x`", + "preferences_save_player_pos_label": "Memorizza il minutaggio raggiunto dal video: ", + "purchased": "Acquistato", + "preferences_quality_option_dash": "DASH (qualità adattiva)", + "preferences_region_label": "Nazione del contenuto: ", + "preferences_category_misc": "Preferenze varie", + "show": "Serie", + "long": "Lungo (> 20 minuti)", + "next_steps_error_message": "Dopodiché dovresti provare a: ", + "next_steps_error_message_refresh": "Aggiornare", + "footer_donate_page": "Dona", + "footer_source_code": "Codice sorgente", + "adminprefs_modified_source_code_url_label": "Link per il repository del codice sorgente modificato", + "Show more": "Mostra di più", + "Broken? Try another Invidious Instance": "Non funzionante? Prova un’altra istanza Invidious" } diff --git a/locales/ja.json b/locales/ja.json index e3014152e..9708c0eae 100644 --- a/locales/ja.json +++ b/locales/ja.json @@ -26,15 +26,15 @@ "No": "いいえ", "Import and Export Data": "データのインポートとエクスポート", "Import": "インポート", - "Import Invidious data": "Invidious データをインポート", - "Import YouTube subscriptions": "YouTube 登録チャンネルをインポート", + "Import Invidious data": "Invidious JSONデータをインポート", + "Import YouTube subscriptions": "YouTube/OPML 登録チャンネルをインポート", "Import FreeTube subscriptions (.db)": "FreeTube 登録チャンネルをインポート (.db)", "Import NewPipe subscriptions (.json)": "NewPipe 登録チャンネルをインポート (.json)", "Import NewPipe data (.zip)": "NewPipe データをインポート (.zip)", "Export": "エクスポート", "Export subscriptions as OPML": "登録チャンネルを OPML でエクスポート", "Export subscriptions as OPML (for NewPipe & FreeTube)": "登録チャンネルを OPML でエクスポート (NewPipe & FreeTube 用)", - "Export data as JSON": "データを JSON でエクスポート", + "Export data as JSON": "Invidious のデータを JSON でエクスポート", "Delete account?": "アカウントを削除しますか?", "History": "履歴", "An alternative front-end to YouTube": "YouTube 向けの代用フロントエンド", @@ -71,7 +71,7 @@ "preferences_related_videos_label": "関連動画を表示: ", "preferences_annotations_label": "デフォルトでアノテーションを表示: ", "preferences_extend_desc_label": "動画の説明文を自動的に拡張: ", - "preferences_vr_mode_label": "対話的な360°動画: ", + "preferences_vr_mode_label": "対話的な360°動画 (WebGL が必要): ", "preferences_category_visual": "外観設定", "preferences_player_style_label": "プレイヤースタイル: ", "Dark mode: ": "ダークモード: ", @@ -411,5 +411,28 @@ "videoinfo_started_streaming_x_ago": "`x`分前に配信を開始", "videoinfo_watch_on_youTube": "YouTube上で見る", "user_created_playlists": "`x`が作成したプレイリスト", - "Video unavailable": "ビデオは利用できません" + "Video unavailable": "ビデオは利用できません", + "Chinese": "中国語", + "Chinese (Taiwan)": "中国語 (台湾)", + "Korean (auto-generated)": "韓国語 (自動生成)", + "Portuguese (auto-generated)": "ポルトガル語 (自動生成)", + "Turkish (auto-generated)": "トルコ語 (自動生成)", + "English (United Kingdom)": "英語 (イギリス)", + "Cantonese (Hong Kong)": "広東語 (香港)", + "Chinese (China)": "中国語 (中国)", + "Chinese (Hong Kong)": "中国語 (香港)", + "Dutch (auto-generated)": "オランダ語 (自動生成)", + "French (auto-generated)": "フランス語 (自動生成)", + "German (auto-generated)": "ドイツ語 (自動生成)", + "Indonesian (auto-generated)": "インドネシア語 (自動生成)", + "Italian (auto-generated)": "イタリア語 (自動生成)", + "Japanese (auto-generated)": "日本語 (自動生成)", + "Interlingue": "インターリング", + "Portuguese (Brazil)": "ポルトガル語 (ブラジル)", + "Russian (auto-generated)": "ロシア語 (自動生成)", + "Spanish (auto-generated)": "スペイン語 (自動生成)", + "Spanish (Mexico)": "スペイン語 (メキシコ)", + "Spanish (Spain)": "スペイン語 (スペイン)", + "Vietnamese (auto-generated)": "ベトナム語 (自動生成)", + "360": "360°" } diff --git a/locales/lt.json b/locales/lt.json index 5b27eae46..a5cee4722 100644 --- a/locales/lt.json +++ b/locales/lt.json @@ -369,5 +369,8 @@ "footer_modfied_source_code": "Pakeistas pirminis kodas", "footer_donate_page": "Paaukoti", "preferences_region_label": "Turinio šalis: ", - "preferences_quality_dash_label": "Pageidaujama DASH vaizdo kokybė: " + "preferences_quality_dash_label": "Pageidaujama DASH vaizdo kokybė: ", + "preferences_quality_dash_option_best": "Geriausia", + "preferences_quality_dash_option_worst": "Blogiausia", + "preferences_quality_dash_option_auto": "Automatinis" } diff --git a/locales/ru.json b/locales/ru.json index 88f813954..c223bcf8f 100644 --- a/locales/ru.json +++ b/locales/ru.json @@ -476,5 +476,6 @@ "360": "360°", "Video unavailable": "Видео недоступно", "preferences_save_player_pos_label": "Запоминать позицию: ", - "preferences_region_label": "Страна: " + "preferences_region_label": "Страна: ", + "preferences_watch_history_label": "Включить историю просмотров " } diff --git a/locales/sv-SE.json b/locales/sv-SE.json index 98c24cc3b..ab0d0773f 100644 --- a/locales/sv-SE.json +++ b/locales/sv-SE.json @@ -327,8 +327,8 @@ "Videos": "Videor", "Playlists": "Spellistor", "Community": "Gemenskap", - "relevance": "relevans", - "rating": "rankning", + "relevance": "Relevans", + "rating": "Rankning", "date": "datum", "views": "visningar", "content_type": "Typ", diff --git a/src/invidious.cr b/src/invidious.cr index 1bdf30974..a470c6b61 100644 --- a/src/invidious.cr +++ b/src/invidious.cr @@ -27,6 +27,7 @@ require "compress/zip" require "protodec/utils" require "./invidious/database/*" +require "./invidious/database/migrations/*" require "./invidious/helpers/*" require "./invidious/yt_backend/*" require "./invidious/frontend/*" @@ -102,6 +103,10 @@ Kemal.config.extra_options do |parser| puts SOFTWARE.to_pretty_json exit end + parser.on("--migrate", "Run any migrations (beta, use at your own risk!!") do + Invidious::Database::Migrator.new(PG_DB).migrate + exit + end end Kemal::CLI.new ARGV diff --git a/src/invidious/database/migration.cr b/src/invidious/database/migration.cr new file mode 100644 index 000000000..921d8f38b --- /dev/null +++ b/src/invidious/database/migration.cr @@ -0,0 +1,38 @@ +abstract class Invidious::Database::Migration + macro inherited + Migrator.migrations << self + end + + @@version : Int64? + + def self.version(version : Int32 | Int64) + @@version = version.to_i64 + end + + getter? completed = false + + def initialize(@db : DB::Database) + end + + abstract def up(conn : DB::Connection) + + def migrate + # migrator already ignores completed migrations + # but this is an extra check to make sure a migration doesn't run twice + return if completed? + + @db.transaction do |txn| + up(txn.connection) + track(txn.connection) + @completed = true + end + end + + def version : Int64 + @@version.not_nil! + end + + private def track(conn : DB::Connection) + conn.exec("INSERT INTO #{Migrator::MIGRATIONS_TABLE} (version) VALUES ($1)", version) + end +end diff --git a/src/invidious/database/migrations/0001_create_channels_table.cr b/src/invidious/database/migrations/0001_create_channels_table.cr new file mode 100644 index 000000000..a1362bcf8 --- /dev/null +++ b/src/invidious/database/migrations/0001_create_channels_table.cr @@ -0,0 +1,30 @@ +module Invidious::Database::Migrations + class CreateChannelsTable < Migration + version 1 + + def up(conn : DB::Connection) + conn.exec <<-SQL + CREATE TABLE IF NOT EXISTS public.channels + ( + id text NOT NULL, + author text, + updated timestamp with time zone, + deleted boolean, + subscribed timestamp with time zone, + CONSTRAINT channels_id_key UNIQUE (id) + ); + SQL + + conn.exec <<-SQL + GRANT ALL ON TABLE public.channels TO current_user; + SQL + + conn.exec <<-SQL + CREATE INDEX IF NOT EXISTS channels_id_idx + ON public.channels + USING btree + (id COLLATE pg_catalog."default"); + SQL + end + end +end diff --git a/src/invidious/database/migrations/0002_create_videos_table.cr b/src/invidious/database/migrations/0002_create_videos_table.cr new file mode 100644 index 000000000..c2ac84f8f --- /dev/null +++ b/src/invidious/database/migrations/0002_create_videos_table.cr @@ -0,0 +1,28 @@ +module Invidious::Database::Migrations + class CreateVideosTable < Migration + version 2 + + def up(conn : DB::Connection) + conn.exec <<-SQL + CREATE UNLOGGED TABLE IF NOT EXISTS public.videos + ( + id text NOT NULL, + info text, + updated timestamp with time zone, + CONSTRAINT videos_pkey PRIMARY KEY (id) + ); + SQL + + conn.exec <<-SQL + GRANT ALL ON TABLE public.videos TO current_user; + SQL + + conn.exec <<-SQL + CREATE UNIQUE INDEX IF NOT EXISTS id_idx + ON public.videos + USING btree + (id COLLATE pg_catalog."default"); + SQL + end + end +end diff --git a/src/invidious/database/migrations/0003_create_channel_videos_table.cr b/src/invidious/database/migrations/0003_create_channel_videos_table.cr new file mode 100644 index 000000000..c9b62e4c6 --- /dev/null +++ b/src/invidious/database/migrations/0003_create_channel_videos_table.cr @@ -0,0 +1,35 @@ +module Invidious::Database::Migrations + class CreateChannelVideosTable < Migration + version 3 + + def up(conn : DB::Connection) + conn.exec <<-SQL + CREATE TABLE IF NOT EXISTS public.channel_videos + ( + id text NOT NULL, + title text, + published timestamp with time zone, + updated timestamp with time zone, + ucid text, + author text, + length_seconds integer, + live_now boolean, + premiere_timestamp timestamp with time zone, + views bigint, + CONSTRAINT channel_videos_id_key UNIQUE (id) + ); + SQL + + conn.exec <<-SQL + GRANT ALL ON TABLE public.channel_videos TO current_user; + SQL + + conn.exec <<-SQL + CREATE INDEX IF NOT EXISTS channel_videos_ucid_idx + ON public.channel_videos + USING btree + (ucid COLLATE pg_catalog."default"); + SQL + end + end +end diff --git a/src/invidious/database/migrations/0004_create_users_table.cr b/src/invidious/database/migrations/0004_create_users_table.cr new file mode 100644 index 000000000..a13ba15f0 --- /dev/null +++ b/src/invidious/database/migrations/0004_create_users_table.cr @@ -0,0 +1,34 @@ +module Invidious::Database::Migrations + class CreateUsersTable < Migration + version 4 + + def up(conn : DB::Connection) + conn.exec <<-SQL + CREATE TABLE IF NOT EXISTS public.users + ( + updated timestamp with time zone, + notifications text[], + subscriptions text[], + email text NOT NULL, + preferences text, + password text, + token text, + watched text[], + feed_needs_update boolean, + CONSTRAINT users_email_key UNIQUE (email) + ); + SQL + + conn.exec <<-SQL + GRANT ALL ON TABLE public.users TO current_user; + SQL + + conn.exec <<-SQL + CREATE UNIQUE INDEX IF NOT EXISTS email_unique_idx + ON public.users + USING btree + (lower(email) COLLATE pg_catalog."default"); + SQL + end + end +end diff --git a/src/invidious/database/migrations/0005_create_session_ids_table.cr b/src/invidious/database/migrations/0005_create_session_ids_table.cr new file mode 100644 index 000000000..13c2228d1 --- /dev/null +++ b/src/invidious/database/migrations/0005_create_session_ids_table.cr @@ -0,0 +1,28 @@ +module Invidious::Database::Migrations + class CreateSessionIdsTable < Migration + version 5 + + def up(conn : DB::Connection) + conn.exec <<-SQL + CREATE TABLE IF NOT EXISTS public.session_ids + ( + id text NOT NULL, + email text, + issued timestamp with time zone, + CONSTRAINT session_ids_pkey PRIMARY KEY (id) + ); + SQL + + conn.exec <<-SQL + GRANT ALL ON TABLE public.session_ids TO current_user; + SQL + + conn.exec <<-SQL + CREATE INDEX IF NOT EXISTS session_ids_id_idx + ON public.session_ids + USING btree + (id COLLATE pg_catalog."default"); + SQL + end + end +end diff --git a/src/invidious/database/migrations/0006_create_nonces_table.cr b/src/invidious/database/migrations/0006_create_nonces_table.cr new file mode 100644 index 000000000..cf1229e16 --- /dev/null +++ b/src/invidious/database/migrations/0006_create_nonces_table.cr @@ -0,0 +1,27 @@ +module Invidious::Database::Migrations + class CreateNoncesTable < Migration + version 6 + + def up(conn : DB::Connection) + conn.exec <<-SQL + CREATE TABLE IF NOT EXISTS public.nonces + ( + nonce text, + expire timestamp with time zone, + CONSTRAINT nonces_id_key UNIQUE (nonce) + ); + SQL + + conn.exec <<-SQL + GRANT ALL ON TABLE public.nonces TO current_user; + SQL + + conn.exec <<-SQL + CREATE INDEX IF NOT EXISTS nonces_nonce_idx + ON public.nonces + USING btree + (nonce COLLATE pg_catalog."default"); + SQL + end + end +end diff --git a/src/invidious/database/migrations/0007_create_annotations_table.cr b/src/invidious/database/migrations/0007_create_annotations_table.cr new file mode 100644 index 000000000..dcecbc3b8 --- /dev/null +++ b/src/invidious/database/migrations/0007_create_annotations_table.cr @@ -0,0 +1,20 @@ +module Invidious::Database::Migrations + class CreateAnnotationsTable < Migration + version 7 + + def up(conn : DB::Connection) + conn.exec <<-SQL + CREATE TABLE IF NOT EXISTS public.annotations + ( + id text NOT NULL, + annotations xml, + CONSTRAINT annotations_id_key UNIQUE (id) + ); + SQL + + conn.exec <<-SQL + GRANT ALL ON TABLE public.annotations TO current_user; + SQL + end + end +end diff --git a/src/invidious/database/migrations/0008_create_playlists_table.cr b/src/invidious/database/migrations/0008_create_playlists_table.cr new file mode 100644 index 000000000..6aa16e1a9 --- /dev/null +++ b/src/invidious/database/migrations/0008_create_playlists_table.cr @@ -0,0 +1,50 @@ +module Invidious::Database::Migrations + class CreatePlaylistsTable < Migration + version 8 + + def up(conn : DB::Connection) + if !privacy_type_exists?(conn) + conn.exec <<-SQL + CREATE TYPE public.privacy AS ENUM + ( + 'Public', + 'Unlisted', + 'Private' + ); + SQL + end + + conn.exec <<-SQL + CREATE TABLE IF NOT EXISTS public.playlists + ( + title text, + id text primary key, + author text, + description text, + video_count integer, + created timestamptz, + updated timestamptz, + privacy privacy, + index int8[] + ); + SQL + + conn.exec <<-SQL + GRANT ALL ON public.playlists TO current_user; + SQL + end + + private def privacy_type_exists?(conn : DB::Connection) : Bool + request = <<-SQL + SELECT 1 AS one + FROM pg_type + INNER JOIN pg_namespace ON pg_namespace.oid = pg_type.typnamespace + WHERE pg_namespace.nspname = 'public' + AND pg_type.typname = 'privacy' + LIMIT 1; + SQL + + !conn.query_one?(request, as: Int32).nil? + end + end +end diff --git a/src/invidious/database/migrations/0009_create_playlist_videos_table.cr b/src/invidious/database/migrations/0009_create_playlist_videos_table.cr new file mode 100644 index 000000000..84938b9ba --- /dev/null +++ b/src/invidious/database/migrations/0009_create_playlist_videos_table.cr @@ -0,0 +1,27 @@ +module Invidious::Database::Migrations + class CreatePlaylistVideosTable < Migration + version 9 + + def up(conn : DB::Connection) + conn.exec <<-SQL + CREATE TABLE IF NOT EXISTS public.playlist_videos + ( + title text, + id text, + author text, + ucid text, + length_seconds integer, + published timestamptz, + plid text references playlists(id), + index int8, + live_now boolean, + PRIMARY KEY (index,plid) + ); + SQL + + conn.exec <<-SQL + GRANT ALL ON TABLE public.playlist_videos TO current_user; + SQL + end + end +end diff --git a/src/invidious/database/migrations/0010_make_videos_unlogged.cr b/src/invidious/database/migrations/0010_make_videos_unlogged.cr new file mode 100644 index 000000000..f5d196834 --- /dev/null +++ b/src/invidious/database/migrations/0010_make_videos_unlogged.cr @@ -0,0 +1,11 @@ +module Invidious::Database::Migrations + class MakeVideosUnlogged < Migration + version 10 + + def up(conn : DB::Connection) + conn.exec <<-SQL + ALTER TABLE public.videos SET UNLOGGED; + SQL + end + end +end diff --git a/src/invidious/database/migrator.cr b/src/invidious/database/migrator.cr new file mode 100644 index 000000000..660c32034 --- /dev/null +++ b/src/invidious/database/migrator.cr @@ -0,0 +1,49 @@ +class Invidious::Database::Migrator + MIGRATIONS_TABLE = "public.invidious_migrations" + + class_getter migrations = [] of Invidious::Database::Migration.class + + def initialize(@db : DB::Database) + end + + def migrate + versions = load_versions + + ran_migration = false + load_migrations.sort_by(&.version) + .each do |migration| + next if versions.includes?(migration.version) + + puts "Running migration: #{migration.class.name}" + migration.migrate + ran_migration = true + end + + puts "No migrations to run." unless ran_migration + end + + def pending_migrations? : Bool + versions = load_versions + + load_migrations.sort_by(&.version) + .any? { |migration| !versions.includes?(migration.version) } + end + + private def load_migrations : Array(Invidious::Database::Migration) + self.class.migrations.map(&.new(@db)) + end + + private def load_versions : Array(Int64) + create_migrations_table + @db.query_all("SELECT version FROM #{MIGRATIONS_TABLE}", as: Int64) + end + + private def create_migrations_table + @db.exec <<-SQL + CREATE TABLE IF NOT EXISTS #{MIGRATIONS_TABLE} ( + id bigserial PRIMARY KEY, + version bigint NOT NULL + ) + SQL + end +end diff --git a/src/invidious/routes/api/v1/search.cr b/src/invidious/routes/api/v1/search.cr index 0b0853b14..5666460d1 100644 --- a/src/invidious/routes/api/v1/search.cr +++ b/src/invidious/routes/api/v1/search.cr @@ -43,20 +43,20 @@ module Invidious::Routes::API::V1::Search end def self.search_suggestions(env) - locale = env.get("preferences").as(Preferences).locale - region = env.params.query["region"]? + preferences = env.get("preferences").as(Preferences) + region = env.params.query["region"]? || preferences.region env.response.content_type = "application/json" - query = env.params.query["q"]? - query ||= "" + query = env.params.query["q"]? || "" begin - headers = HTTP::Headers{":authority" => "suggestqueries.google.com"} - response = YT_POOL.client &.get("/complete/search?hl=en&gl=#{region}&client=youtube&ds=yt&q=#{URI.encode_www_form(query)}&callback=suggestCallback", headers).body + 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" - body = response[35..-2] - body = JSON.parse(body).as_a + response = client.get(url).body + + body = JSON.parse(response[5..-1]).as_a suggestions = body[1].as_a[0..-2] JSON.build do |json|