diff options
| -rw-r--r-- | .github/workflows/ci.yml | 16 | ||||
| -rw-r--r-- | .github/workflows/container-release.yml | 20 | ||||
| -rw-r--r-- | .github/workflows/stale.yml | 4 | ||||
| -rw-r--r-- | README.md | 4 | ||||
| -rw-r--r-- | locales/ar.json | 3 | ||||
| -rw-r--r-- | locales/cs.json | 2 | ||||
| -rw-r--r-- | locales/et.json | 338 | ||||
| -rw-r--r-- | locales/hi.json | 2 | ||||
| -rw-r--r-- | locales/pt-BR.json | 2 | ||||
| -rw-r--r-- | src/invidious/channels/about.cr | 20 | ||||
| -rw-r--r-- | src/invidious/comments.cr | 46 | ||||
| -rw-r--r-- | src/invidious/config.cr | 5 | ||||
| -rw-r--r-- | src/invidious/helpers/i18n.cr | 1 | ||||
| -rw-r--r-- | src/invidious/routes/watch.cr | 19 | ||||
| -rw-r--r-- | src/invidious/views/watch.ecr | 4 |
15 files changed, 441 insertions, 45 deletions
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 4e68b7f2..6107e260 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -46,15 +46,15 @@ jobs: stable: false steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - name: Install Crystal - uses: crystal-lang/install-crystal@v1.5.3 + uses: crystal-lang/install-crystal@v1.6.0 with: crystal: ${{ matrix.crystal }} - name: Cache Shards - uses: actions/cache@v2 + uses: actions/cache@v3 with: path: ./lib key: shards-${{ hashFiles('shard.lock') }} @@ -84,7 +84,7 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - name: Build Docker run: docker-compose build --build-arg release=0 @@ -100,18 +100,18 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - name: Set up QEMU - uses: docker/setup-qemu-action@v1 + uses: docker/setup-qemu-action@v2 with: platforms: arm64 - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v1 + uses: docker/setup-buildx-action@v2 - name: Build Docker ARM64 image - uses: docker/build-push-action@v2 + uses: docker/build-push-action@v3 with: context: . file: docker/Dockerfile.arm64 diff --git a/.github/workflows/container-release.yml b/.github/workflows/container-release.yml index 36fb566e..212487c8 100644 --- a/.github/workflows/container-release.yml +++ b/.github/workflows/container-release.yml @@ -15,20 +15,20 @@ on: - screenshots/* - .github/ISSUE_TEMPLATE/* - kubernetes/** - + jobs: release: runs-on: ubuntu-latest steps: - name: Checkout - uses: actions/checkout@v2 - + uses: actions/checkout@v3 + - name: Install Crystal - uses: oprypin/install-crystal@v1.2.4 + uses: crystal-lang/install-crystal@v1.6.0 with: crystal: 1.2.2 - + - name: Run lint run: | if ! crystal tool format --check; then @@ -38,15 +38,15 @@ jobs: fi - name: Set up QEMU - uses: docker/setup-qemu-action@v1 + uses: docker/setup-qemu-action@v2 with: platforms: arm64 - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v1 + uses: docker/setup-buildx-action@v2 - name: Login to registry - uses: docker/login-action@v1 + uses: docker/login-action@v2 with: registry: quay.io username: ${{ secrets.QUAY_USERNAME }} @@ -54,7 +54,7 @@ jobs: - name: Build and push Docker AMD64 image for Push Event if: github.ref == 'refs/heads/master' - uses: docker/build-push-action@v2 + uses: docker/build-push-action@v3 with: context: . file: docker/Dockerfile @@ -66,7 +66,7 @@ jobs: - name: Build and push Docker ARM64 image for Push Event if: github.ref == 'refs/heads/master' - uses: docker/build-push-action@v2 + uses: docker/build-push-action@v3 with: context: . file: docker/Dockerfile.arm64 diff --git a/.github/workflows/stale.yml b/.github/workflows/stale.yml index 86275da7..ff28d49b 100644 --- a/.github/workflows/stale.yml +++ b/.github/workflows/stale.yml @@ -10,11 +10,11 @@ jobs: stale: runs-on: ubuntu-latest steps: - - uses: actions/stale@v3 + - uses: actions/stale@v5 with: repo-token: ${{ secrets.GITHUB_TOKEN }} days-before-stale: 365 - days-before-pr-stale: 45 # PRs should be active. Anything that hasn't had activity in more than 45 days should be considered abandoned. + days-before-pr-stale: 45 # PRs should be active. Anything that hasn't had activity in more than 45 days should be considered abandoned. days-before-close: 30 exempt-pr-labels: blocked stale-issue-message: 'This issue has been automatically marked as stale and will be closed in 30 days because it has not had recent activity and is much likely outdated. If you think this issue is still relevant and applicable, you just have to post a comment and it will be unmarked.' @@ -48,7 +48,7 @@ </a> <br> <a rel="me" href="https://social.tchncs.de/@invidious"> - <img alt="Mastodon: @invidious@social.tchncs.de" src="https://img.shields.io/badge/Mastodon-%40invidious%40social.tchncs.de-darkgreen"> + <img alt="Fediverse: @invidious@social.tchncs.de" src="https://img.shields.io/badge/Fediverse-%40invidious%40social.tchncs.de-darkgreen"> </a> <br> <a href="https://invidious.io/contact/"> @@ -151,6 +151,8 @@ Weblate also allows you to log-in with major SSO providers like Github, Gitlab, - [MusicPiped](https://github.com/deep-gaurav/MusicPiped): A material design music player that streams music from YouTube. - [HoloPlay](https://github.com/stephane-r/HoloPlay): Funny Android application connecting on Invidious API's with search, playlists and favorites. - [WatchTube](https://github.com/WatchTubeTeam/WatchTube): Powerful YouTube client for Apple Watch. +- [Yattee](https://github.com/yattee/yattee): Alternative YouTube frontend for iPhone, iPad, Mac and Apple TV. +- [TubiTui](https://codeberg.org/777/TubiTui): A lightweight, libre, TUI-based YouTube client. ## Liability diff --git a/locales/ar.json b/locales/ar.json index 01c9bbb9..c6ed19ce 100644 --- a/locales/ar.json +++ b/locales/ar.json @@ -458,5 +458,6 @@ "Russian (auto-generated)": "الروسية (منشأة تلقائيا)", "Spanish (Spain)": "الإسبانية (إسبانيا)", "crash_page_search_issue": "بحثت عن <a href=\"`x`\"> المشكلات الموجودة على GitHub </a>", - "search_filters_title": "معامل الفرز" + "search_filters_title": "معامل الفرز", + "search_message_no_results": "لا توجد نتائج." } diff --git a/locales/cs.json b/locales/cs.json index 318866b1..d590b5b8 100644 --- a/locales/cs.json +++ b/locales/cs.json @@ -61,7 +61,7 @@ "preferences_comments_label": "Předpřipravené komentáře: ", "youtube": "YouTube", "reddit": "Reddit", - "preferences_captions_label": "Standartní Titulky: ", + "preferences_captions_label": "Výchozí titulky: ", "Fallback captions: ": "Záložní titulky: ", "preferences_related_videos_label": "Zobrazit podobné videa: ", "preferences_annotations_label": "Zobrazovat poznámky ve výchozím nastavení: ", diff --git a/locales/et.json b/locales/et.json new file mode 100644 index 00000000..7beb1749 --- /dev/null +++ b/locales/et.json @@ -0,0 +1,338 @@ +{ + "generic_playlists_count": "{{count}} esitusloend", + "generic_playlists_count_plural": "{{count}} esindusloendit", + "LIVE": "OTSEÜLEKANNE", + "View channel on YouTube": "Vaata kanalit YouTube'is", + "Log in": "Logi sisse", + "Log in/register": "Logi sisse/registreeru", + "Dark mode: ": "Tume režiim: ", + "generic_videos_count": "{{count}} video", + "generic_videos_count_plural": "{{count}} videot", + "generic_subscribers_count": "{{count}} tellija", + "generic_subscribers_count_plural": "{{count}} tellijat", + "generic_subscriptions_count": "{{count}} tellimus", + "generic_subscriptions_count_plural": "{{count}} tellimust", + "Shared `x` ago": "Jagatud `x` tagasi", + "Unsubscribe": "Loobu tellimusest", + "Subscribe": "Telli", + "View playlist on YouTube": "Vaata esitusloendit YouTube'is", + "newest": "uusimad", + "oldest": "vanimad", + "popular": "populaarsed", + "last": "viimane", + "Next page": "Järgmine leht", + "Previous page": "Eelmine leht", + "Clear watch history?": "Kustuta vaatamiste ajalugu?", + "New password": "Uus salasõna", + "New passwords must match": "Uued salasõnad peavad ühtima", + "Cannot change password for Google accounts": "Google'i kasutaja salasõna ei saa muuta", + "Import and Export Data": "Impordi ja ekspordi andmed", + "Import": "Impordi", + "Import YouTube subscriptions": "Impordi tellimused Youtube'ist/OPML-ist", + "Import FreeTube subscriptions (.db)": "Impordi tellimused FreeTube'ist (.db)", + "Import NewPipe data (.zip)": "Impordi NewPipe'i andmed (.zip)", + "Export": "Ekspordi", + "Export subscriptions as OPML": "Ekspordi tellimused OPML-ina", + "Export subscriptions as OPML (for NewPipe & FreeTube)": "Ekspordi tellimused OPML-ina (NewPipe'i ja FreeTube'i jaoks)", + "Delete account?": "Kustuta kasutaja?", + "History": "Ajalugu", + "JavaScript license information": "JavaScripti litsentsi info", + "source": "allikas", + "Log in with Google": "Logi sisse Google'iga", + "User ID": "Kasutada ID", + "Password": "Salasõna", + "Time (h:mm:ss):": "Aeg (h:mm:ss):", + "Text CAPTCHA": "CAPTCHA-tekst", + "Image CAPTCHA": "CAPTCHA-foto", + "Sign In": "Logi sisse", + "Register": "Registreeru", + "E-mail": "E-post", + "Preferences": "Eelistused", + "preferences_category_player": "Mängija eelistused", + "preferences_continue_autoplay_label": "Mängi järgmine video automaatselt: ", + "preferences_quality_label": "Eelistatud videokvaliteet: ", + "preferences_quality_option_dash": "DASH (kohanduv kvaliteet)", + "preferences_quality_option_hd720": "HD720", + "preferences_quality_option_medium": "Keskmine", + "preferences_quality_option_small": "Väike", + "preferences_quality_dash_label": "Eelistatav DASH-video kvaliteet: ", + "preferences_quality_dash_option_auto": "Automaatne", + "preferences_quality_dash_option_best": "Parim", + "preferences_quality_dash_option_worst": "Halvim", + "preferences_volume_label": "Video helitugevus: ", + "youtube": "YouTube", + "reddit": "Reddit", + "preferences_related_videos_label": "Näita sarnaseid videosid: ", + "preferences_vr_mode_label": "Interaktiivne 360-kraadine video (vajalik WebGL): ", + "preferences_dark_mode_label": "Teema: ", + "dark": "tume", + "light": "hele", + "preferences_category_subscription": "Tellimuse seaded", + "preferences_max_results_label": "Avalehel näidatavate videote arv: ", + "preferences_sort_label": "Sorteeri: ", + "published": "avaldatud", + "alphabetically": "tähestikulises järjekorras", + "alphabetically - reverse": "vastupidi tähestikulises järjekorras", + "channel name": "kanali nimi", + "preferences_unseen_only_label": "Näita ainult vaatamata videosid: ", + "Only show latest video from channel: ": "Näita ainult viimast videot: ", + "preferences_notifications_only_label": "Näita ainult teavitusi (kui neid on): ", + "Enable web notifications": "Luba veebiteavitused", + "`x` uploaded a video": "`x` laadis video üles", + "`x` is live": "`x` teeb otseülekannet", + "preferences_category_data": "Andme-eelistused", + "Clear watch history": "Puhasta vaatamisajalugu", + "Import/export data": "Impordi/ekspordi andmed", + "Change password": "Muuda salasõna", + "Watch history": "Vaatamisajalugu", + "Delete account": "Kustuta kasutaja", + "Save preferences": "Salvesta eelistused", + "Token": "Token", + "Import/export": "Imprort/eksport", + "unsubscribe": "loobu tellimusest", + "Subscriptions": "Tellimused", + "search": "otsi", + "Source available here.": "Allikas on kättesaadaval siin.", + "View privacy policy.": "Vaata privaatsuspoliitikat.", + "Public": "Avalik", + "Private": "Privaatne", + "View all playlists": "Vaata kõiki esitusloendeid", + "Updated `x` ago": "Uuendas `x` tagasi", + "Delete playlist `x`?": "Kustuta esitusloend `x`?", + "Delete playlist": "Kustuta esitusloend", + "Create playlist": "Loo esitlusloend", + "Title": "Pealkiri", + "Playlist privacy": "Esitusloendi privaatsus", + "Show more": "Näita rohkem", + "Show less": "Näita vähem", + "Watch on YouTube": "Vaata YouTube'is", + "search_message_no_results": "Tulemusi ei leitud.", + "search_message_change_filters_or_query": "Proovi otsingut laiendada või filtreid muuta.", + "Genre: ": "Žanr: ", + "License: ": "Litsents: ", + "Family friendly? ": "Peresõbralik? ", + "Shared `x`": "Jagas `x`", + "Premieres in `x`": "Esilinastub `x`", + "Hi! Looks like you have JavaScript turned off. Click here to view comments, keep in mind they may take a bit longer to load.": "Tundub, et oled JavaScripti välja lülitanud. Vajuta siia, et kommentaare vaadata; nende laadimine võib võtta natukene rohkem aega.", + "View Reddit comments": "Vaata Redditi kommentaare", + "Hide replies": "Peida vastused", + "Show replies": "Näita vastuseid", + "Incorrect password": "Vale salasõna", + "Login failed. This may be because two-factor authentication is not turned on for your account.": "Sisselogimine ei õnnestunud. Asi võib olla selles, et", + "Wrong answer": "Vale vastus", + "User ID is a required field": "Kasutaja ID on kohustuslik väli", + "Password is a required field": "Salasõna on kohustuslik väli", + "Wrong username or password": "Vale kasutajanimi või salasõna", + "Please sign in using 'Log in with Google'": "Palun kasutage 'Logi sisse Google'iga'", + "Password cannot be longer than 55 characters": "Salasõna ei tohi olla pikem kui 55 tähemärki", + "Password cannot be empty": "Salasõna ei tohi olla tühi", + "Please log in": "Palun logige sisse", + "channel:`x`": "kanal:`x`", + "Deleted or invalid channel": "Kanal on kustutatud või seda ei leitud", + "This channel does not exist.": "Sellist kanalit pole olemas.", + "comments_view_x_replies": "{{count}} vastus", + "comments_view_x_replies_plural": "{{count}} vastust", + "`x` ago": "`x` tagasi", + "Load more": "Laadi rohkem", + "Empty playlist": "Tühi esitusloend", + "Not a playlist.": "Tegu pole esitusloendiga.", + "Playlist does not exist.": "Seda esitusloendit pole olemas.", + "No such user": "Sellist kasutajat pole", + "English": "Inglise", + "English (United Kingdom)": "Inglise (Suurbritannia)", + "English (United States)": "Inglise (USA)", + "English (auto-generated)": "Inglise (automaatselt koostatud)", + "Afrikaans": "Afrikaani", + "Albanian": "Albaania", + "Arabic": "Araabia", + "Armenian": "Armeenia", + "Bangla": "Bengali", + "Basque": "Baski", + "Belarusian": "Valgevene", + "Bulgarian": "Bulgaaria", + "Burmese": "Birma", + "Cantonese (Hong Kong)": "Kantoni (Hong Konk)", + "Chinese (China)": "Hiina (Hiina)", + "Chinese (Hong Kong)": "Hiina (Hong Kong)", + "Chinese (Simplified)": "Hiina (lihtsustatud)", + "Chinese (Taiwan)": "Hiina (Taiwan)", + "Croatian": "Horvaatia", + "Czech": "Tšehhi", + "Danish": "Taani", + "Dutch": "Hollandi", + "Esperanto": "Esperanto", + "Estonian": "Eesti", + "Filipino": "Filipiini", + "Finnish": "Soome", + "French": "Prantsuse", + "French (auto-generated)": "Prantsuse (automaatne)", + "Dutch (auto-generated)": "Hollandi (automaatne)", + "Galician": "Kaliitsia", + "Georgian": "Gruusia", + "Haitian Creole": "Haiti kreool", + "Hausa": "Hausa", + "Hawaiian": "Havaii", + "Hebrew": "Heebrea", + "Hindi": "Hindi", + "Hungarian": "Ungari", + "Icelandic": "Islandi", + "Indonesian": "Indoneesia", + "Japanese (auto-generated)": "Jaapani (automaatne)", + "Kannada": "Kannada", + "Kazakh": "Kasahhi", + "Luxembourgish": "Luksemburgi", + "Macedonian": "Makedoonia", + "Malay": "Malai", + "Maltese": "Malta", + "Maori": "Maori", + "Marathi": "Marathi", + "Mongolian": "Mongoli", + "Nepali": "Nepaali", + "Norwegian Bokmål": "Norra (Bokmål)", + "Persian": "Pärsia", + "Polish": "Poola", + "Portuguese": "Portugali", + "Portuguese (auto-generated)": "Portugali (automaatne)", + "Portuguese (Brazil)": "Portugali (Brasiilia)", + "Romanian": "Rumeenia", + "Russian": "Vene", + "Russian (auto-generated)": "Vene (automaatne)", + "Scottish Gaelic": "Šoti (Gaeli)", + "Serbian": "Serbia", + "Slovak": "Slovaki", + "Slovenian": "Sloveeni", + "Somali": "Somaali", + "Spanish": "Hispaania", + "Spanish (auto-generated)": "Hispaania (automaatne)", + "Spanish (Latin America)": "Hispaania (Ladina-Ameerika)", + "Spanish (Mexico)": "Hispaania (Mehhiko)", + "Spanish (Spain)": "Hispaania (Hispaania)", + "Swahili": "Suahili", + "Swedish": "Rootsi", + "Tajik": "Tadžiki", + "Tamil": "Tamiili", + "Thai": "Tai", + "Turkish": "Türgi", + "Turkish (auto-generated)": "Türgi (automaatne)", + "Ukrainian": "Ukraina", + "Uzbek": "Usbeki", + "Vietnamese": "Vietnami", + "Vietnamese (auto-generated)": "Vietnami (automaatne)", + "generic_count_years": "{{count}} aasta", + "generic_count_years_plural": "{{count}} aastat", + "generic_count_months": "{{count}} kuu", + "generic_count_months_plural": "{{count}} kuud", + "generic_count_weeks": "{{count}} nädal", + "generic_count_weeks_plural": "{{count}} nädalat", + "generic_count_days": "{{count}} päev", + "generic_count_days_plural": "{{count}} päeva", + "generic_count_hours": "{{count}} tund", + "generic_count_hours_plural": "{{count}} tundi", + "generic_count_minutes": "{{count}} minut", + "generic_count_minutes_plural": "{{count}} minutit", + "Popular": "Populaarne", + "Search": "Otsi", + "Top": "Top", + "About": "Leheküljest", + "preferences_locale_label": "Keel: ", + "View as playlist": "Vaata esitusloendina", + "Movies": "Filmid", + "Download as: ": "Laadi kui: ", + "(edited)": "(muudetud)", + "`x` marked it with a ❤": "`x` märkis ❤", + "Audio mode": "Audiorežiim", + "Video mode": "Videorežiim", + "search_filters_date_label": "Üleslaadimise kuupäev", + "search_filters_date_option_none": "Ükskõik mis kuupäev", + "search_filters_date_option_today": "Täna", + "search_filters_date_option_week": "Sel nädalal", + "search_filters_date_option_hour": "Viimasel tunnil", + "search_filters_date_option_month": "Sel kuul", + "search_filters_date_option_year": "Sel aastal", + "search_filters_type_label": "Tüüp", + "search_filters_type_option_all": "Ükskõik mis tüüp", + "search_filters_duration_label": "Kestus", + "search_filters_type_option_show": "Näita", + "search_filters_duration_option_none": "Ükskõik mis kestus", + "search_filters_duration_option_short": "Lühike (alla 4 minuti)", + "search_filters_duration_option_medium": "Keskmine (4 - 20 minutit)", + "search_filters_duration_option_long": "Pikk (üle 20 minuti)", + "search_filters_features_option_live": "Otseülekanne", + "search_filters_features_option_four_k": "4K", + "search_filters_features_option_hd": "HD", + "search_filters_features_option_subtitles": "Subtiitrid", + "search_filters_features_option_location": "Asukoht", + "search_filters_sort_label": "Sorteeri", + "search_filters_sort_option_views": "Vaatamiste arv", + "next_steps_error_message": "Pärast mida võiksite proovida: ", + "videoinfo_started_streaming_x_ago": "Alustas otseülekannet `x` tagasi", + "Yes": "Jah", + "generic_views_count": "{{count}} vaatamine", + "generic_views_count_plural": "{{count}} vaatamist", + "Import NewPipe subscriptions (.json)": "Impordi tellimused NewPipe'ist (.json)", + "No": "Ei", + "preferences_region_label": "Riik: ", + "View YouTube comments": "Vaata YouTube'i kommentaare", + "preferences_extend_desc_label": "Ava video kirjeldus automaatselt: ", + "German (auto-generated)": "Saksa (automaatne)", + "Italian": "Itaalia", + "preferences_player_style_label": "Mängija stiil: ", + "subscriptions_unseen_notifs_count": "{{count}} lugemata teavitus", + "subscriptions_unseen_notifs_count_plural": "{{count}} lugemata teavitust", + "View more comments on Reddit": "Vaata teisi kommentaare Redditis", + "Only show latest unwatched video from channel: ": "Näita ainult viimast vaatamata videot: ", + "tokens_count": "{{count}} token", + "tokens_count_plural": "{{count}} tokenit", + "Log out": "Logi välja", + "Premieres `x`": "Linastub`x`", + "View `x` comments": { + "([^.,0-9]|^)1([^.,0-9]|$)": "Vaata `x` kommentaari", + "": "Vaata `x` kommentaare" + }, + "Khmer": "Khmeeri", + "Unable to log in, make sure two-factor authentication (Authenticator or SMS) is turned on.": "Sisselogimine ei õnnestunud. Kontrollige, kas two-factor authentication (Authenticator või SMS) on sisselülitatud.", + "Invalid TFA code": "Vale TFA-kood", + "Bosnian": "Bosnia", + "Corsican": "Korsika", + "Javanese": "Jaava", + "Lithuanian": "Leedu", + "Videos": "Videod", + "Community": "Kogukond", + "CAPTCHA is a required field": "CAPTCHA on kohustuslik väli", + "comments_points_count": "{{count}} punkt", + "comments_points_count_plural": "{{count}} punkti", + "Chinese": "Hiina", + "German": "Saksa", + "Indonesian (auto-generated)": "Indoneesia (automaatne)", + "Italian (auto-generated)": "Itaalia (automaatne)", + "Kyrgyz": "Kirkiisi", + "Latin": "Ladina", + "generic_count_seconds": "{{count}} sekund", + "generic_count_seconds_plural": "{{count}} sekundit", + "Catalan": "Katalaani", + "Chinese (Traditional)": "Hiina (traditsiooniline)", + "Greek": "Kreeka", + "Kurdish": "Kurdi", + "Latvian": "Läti", + "Irish": "Iiri", + "Korean": "Korea", + "Japanese": "Jaapani", + "Korean (auto-generated)": "Korea (automaatne)", + "Music": "Muusika", + "Playlists": "Esitusloendid", + "search_filters_type_option_video": "Video", + "search_filters_sort_option_date": "Üleslaadimise kuupäev", + "Current version: ": "Praegune versioon: ", + "footer_documentation": "Dokumentatsioon", + "Gaming": "Mängud", + "News": "Uudised", + "Download": "Laadi alla", + "search_filters_title": "Filtrid", + "search_filters_type_option_channel": "Kanal", + "search_filters_type_option_playlist": "Esitusloend", + "search_filters_type_option_movie": "Film", + "next_steps_error_message_go_to_youtube": "Minna YouTube'i", + "next_steps_error_message_refresh": "Laadida uuesti", + "footer_donate_page": "Anneta", + "videoinfo_watch_on_youTube": "Vaata YouTube'is" +} diff --git a/locales/hi.json b/locales/hi.json index 0fc35b25..32ae7823 100644 --- a/locales/hi.json +++ b/locales/hi.json @@ -462,7 +462,7 @@ "preferences_save_player_pos_label": "यहाँ से चलाना शुरू करें: ", "crash_page_you_found_a_bug": "शायद आपको Invidious में कोई बग नज़र आ गया है!", "videoinfo_youTube_embed_link": "एम्बेड करें", - "videoinfo_invidious_embed_link": "एम्बोड करने की कड़ी", + "videoinfo_invidious_embed_link": "एम्बेड करने की कड़ी", "download_subtitles": "उपशीर्षक - `x` (.vtt)", "user_created_playlists": "बनाए गए `x` प्लेलिस्ट्स", "user_saved_playlists": "सहेजे गए `x` प्लेलिस्ट्स", diff --git a/locales/pt-BR.json b/locales/pt-BR.json index e54e20bd..df149564 100644 --- a/locales/pt-BR.json +++ b/locales/pt-BR.json @@ -461,7 +461,7 @@ "search_filters_date_option_none": "Qualquer data", "Dutch (auto-generated)": "Holandês (gerado automaticamente)", "French (auto-generated)": "Francês (gerado automaticamente)", - "Indonesian (auto-generated)": "indonésio (gerado automaticamente)", + "Indonesian (auto-generated)": "Indonésio (gerado automaticamente)", "Italian (auto-generated)": "Italiano (gerado automaticamente)", "Spanish (auto-generated)": "Espanhol (gerado automaticamente)", "Spanish (Mexico)": "Espanhol (México)", diff --git a/src/invidious/channels/about.cr b/src/invidious/channels/about.cr index d48fd1fb..da71e9a8 100644 --- a/src/invidious/channels/about.cr +++ b/src/invidious/channels/about.cr @@ -6,6 +6,7 @@ record AboutChannel, author_url : String, author_thumbnail : String, banner : String?, + description : String, description_html : String, total_views : Int64, sub_count : Int32, @@ -52,8 +53,7 @@ def get_about_info(ucid, locale) : AboutChannel banners = initdata["header"]["interactiveTabbedHeaderRenderer"]?.try &.["banner"]?.try &.["thumbnails"]? banner = banners.try &.[-1]?.try &.["url"].as_s? - description = initdata["header"]["interactiveTabbedHeaderRenderer"]["description"]["simpleText"].as_s - description_html = HTML.escape(description) + description_node = initdata["header"]["interactiveTabbedHeaderRenderer"]["description"] is_family_friendly = initdata["microformat"]["microformatDataRenderer"]["familySafe"].as_bool allowed_regions = initdata["microformat"]["microformatDataRenderer"]["availableCountries"].as_a.map(&.as_s) @@ -75,13 +75,24 @@ def get_about_info(ucid, locale) : AboutChannel author_verified_badge = initdata["header"].dig?("c4TabbedHeaderRenderer", "badges", 0, "metadataBadgeRenderer", "tooltip") author_verified = (author_verified_badge && author_verified_badge == "Verified") - description = initdata["metadata"]["channelMetadataRenderer"]?.try &.["description"]?.try &.as_s? || "" - description_html = HTML.escape(description) + description_node = initdata["metadata"]["channelMetadataRenderer"]?.try &.["description"]? is_family_friendly = initdata["microformat"]["microformatDataRenderer"]["familySafe"].as_bool allowed_regions = initdata["microformat"]["microformatDataRenderer"]["availableCountries"].as_a.map(&.as_s) end + description = !description_node.nil? ? description_node.as_s : "" + description_html = HTML.escape(description) + if !description_node.nil? + if description_node.as_h?.nil? + description_node = text_to_parsed_content(description_node.as_s) + end + description_html = parse_content(description_node) + if description_html == "" && description != "" + description_html = HTML.escape(description) + end + end + total_views = 0_i64 joined = Time.unix(0) @@ -125,6 +136,7 @@ def get_about_info(ucid, locale) : AboutChannel author_url: author_url, author_thumbnail: author_thumbnail, banner: banner, + description: description, description_html: description_html, total_views: total_views, sub_count: sub_count, diff --git a/src/invidious/comments.cr b/src/invidious/comments.cr index f50b5907..15a15224 100644 --- a/src/invidious/comments.cr +++ b/src/invidious/comments.cr @@ -560,6 +560,48 @@ def fill_links(html, scheme, host) return html.to_xml(options: XML::SaveOptions::NO_DECL) end +def text_to_parsed_content(text : String) : JSON::Any + nodes = [] of JSON::Any + # For each line convert line to array of nodes + text.split('\n').each do |line| + # In first case line is just a simple node before + # check patterns inside line + # { 'text': line } + currentNodes = [] of JSON::Any + initialNode = {"text" => line} + currentNodes << (JSON.parse(initialNode.to_json)) + + # For each match with url pattern, get last node and preserve + # last node before create new node with url information + # { 'text': match, 'navigationEndpoint': { 'urlEndpoint' : 'url': match } } + line.scan(/https?:\/\/[^ ]*/).each do |urlMatch| + # Retrieve last node and update node without match + lastNode = currentNodes[currentNodes.size - 1].as_h + splittedLastNode = lastNode["text"].as_s.split(urlMatch[0]) + lastNode["text"] = JSON.parse(splittedLastNode[0].to_json) + currentNodes[currentNodes.size - 1] = JSON.parse(lastNode.to_json) + # Create new node with match and navigation infos + currentNode = {"text" => urlMatch[0], "navigationEndpoint" => {"urlEndpoint" => {"url" => urlMatch[0]}}} + currentNodes << (JSON.parse(currentNode.to_json)) + # If text remain after match create new simple node with text after match + afterNode = {"text" => splittedLastNode.size > 0 ? splittedLastNode[1] : ""} + currentNodes << (JSON.parse(afterNode.to_json)) + end + + # After processing of matches inside line + # Add \n at end of last node for preserve carriage return + lastNode = currentNodes[currentNodes.size - 1].as_h + lastNode["text"] = JSON.parse("#{currentNodes[currentNodes.size - 1]["text"]}\n".to_json) + currentNodes[currentNodes.size - 1] = JSON.parse(lastNode.to_json) + + # Finally add final nodes to nodes returned + currentNodes.each do |node| + nodes << (node) + end + end + return JSON.parse({"runs" => nodes}.to_json) +end + def parse_content(content : JSON::Any, video_id : String? = "") : String content["simpleText"]?.try &.as_s.rchop('\ufeff').try { |b| HTML.escape(b) }.to_s || content["runs"]?.try &.as_a.try { |r| content_to_comment_html(r, video_id).try &.to_s.gsub("\n", "<br>") } || "" @@ -567,6 +609,10 @@ end def content_to_comment_html(content, video_id : String? = "") html_array = content.map do |run| + # Sometimes, there is an empty element. + # See: https://github.com/iv-org/invidious/issues/3096 + next if run.as_h.empty? + text = HTML.escape(run["text"].as_s) if run["navigationEndpoint"]? diff --git a/src/invidious/config.cr b/src/invidious/config.cr index 93c4c0f7..a077c7fd 100644 --- a/src/invidious/config.cr +++ b/src/invidious/config.cr @@ -161,16 +161,13 @@ class Config {% env_id = "INVIDIOUS_#{ivar.id.upcase}" %} if ENV.has_key?({{env_id}}) - # puts %(Config.{{ivar.id}} : Loading from env var {{env_id}}) env_value = ENV.fetch({{env_id}}) success = false # Use YAML converter if specified {% ann = ivar.annotation(::YAML::Field) %} {% if ann && ann[:converter] %} - puts %(Config.{{ivar.id}} : Parsing "#{env_value}" as {{ivar.type}} with {{ann[:converter]}} converter) config.{{ivar.id}} = {{ann[:converter]}}.from_yaml(YAML::ParseContext.new, YAML::Nodes.parse(ENV.fetch({{env_id}})).nodes[0]) - puts %(Config.{{ivar.id}} : Set to #{config.{{ivar.id}}}) success = true # Use regular YAML parser otherwise @@ -181,9 +178,7 @@ class Config {{ivar_types}}.each do |ivar_type| if !success begin - # puts %(Config.{{ivar.id}} : Trying to parse "#{env_value}" as #{ivar_type}) config.{{ivar.id}} = ivar_type.from_yaml(env_value) - puts %(Config.{{ivar.id}} : Set to #{config.{{ivar.id}}} (#{ivar_type})) success = true rescue # nop diff --git a/src/invidious/helpers/i18n.cr b/src/invidious/helpers/i18n.cr index 9d3c4e8b..fd86594c 100644 --- a/src/invidious/helpers/i18n.cr +++ b/src/invidious/helpers/i18n.cr @@ -10,6 +10,7 @@ LOCALES_LIST = { "en-US" => "English", # English "eo" => "Esperanto", # Esperanto "es" => "Español", # Spanish + "et" => "Eesti keel", # Estonian "fa" => "فارسی", # Persian "fi" => "Suomi", # Finnish "fr" => "Français", # French diff --git a/src/invidious/routes/watch.cr b/src/invidious/routes/watch.cr index 867ffa6a..7280de4f 100644 --- a/src/invidious/routes/watch.cr +++ b/src/invidious/routes/watch.cr @@ -306,27 +306,28 @@ module Invidious::Routes::Watch download_widget = JSON.parse(selection) extension = download_widget["ext"].as_s - filename = "#{video_id}-#{title}.#{extension}" + filename = "#{title}-#{video_id}.#{extension}" - # Pass form parameters as URL parameters for the handlers of both - # /latest_version and /api/v1/captions. This avoids an un-necessary - # redirect and duplicated (and hazardous) sanity checks. - env.params.query["id"] = video_id - env.params.query["title"] = filename - - # Delete the useless ones + # Delete the now useless URL parameters env.params.body.delete("id") env.params.body.delete("title") env.params.body.delete("download_widget") + # Pass form parameters as URL parameters for the handlers of both + # /latest_version and /api/v1/captions. This avoids an un-necessary + # redirect and duplicated (and hazardous) sanity checks. if label = download_widget["label"]? # URL params specific to /api/v1/captions/:id - env.params.query["label"] = URI.encode_www_form(label.as_s, space_to_plus: false) + env.params.url["id"] = video_id + env.params.query["title"] = filename + env.params.query["label"] = URI.decode_www_form(label.as_s) return Invidious::Routes::API::V1::Videos.captions(env) elsif itag = download_widget["itag"]?.try &.as_i # URL params specific to /latest_version + env.params.query["id"] = video_id env.params.query["itag"] = itag.to_s + env.params.query["title"] = filename env.params.query["local"] = "true" return Invidious::Routes::VideoPlayback.latest_version(env) diff --git a/src/invidious/views/watch.ecr b/src/invidious/views/watch.ecr index 861b2048..d1fdcce2 100644 --- a/src/invidious/views/watch.ecr +++ b/src/invidious/views/watch.ecr @@ -173,7 +173,7 @@ we're going to need to do it here in order to allow for translations. <p id="views"><i class="icon ion-ios-eye"></i> <%= number_with_separator(video.views) %></p> <p id="likes"><i class="icon ion-ios-thumbs-up"></i> <%= number_with_separator(video.likes) %></p> - <p id="dislikes"><i class="icon ion-ios-thumbs-down"></i> <%= number_with_separator(video.dislikes) %></p> + <p id="dislikes"></p> <p id="genre"><%= translate(locale, "Genre: ") %> <% if !video.genre_url %> <%= video.genre %> @@ -186,7 +186,7 @@ we're going to need to do it here in order to allow for translations. <% end %> <p id="family_friendly"><%= translate(locale, "Family friendly? ") %><%= translate_bool(locale, video.is_family_friendly) %></p> <p id="wilson"><%= translate(locale, "Wilson score: ") %><%= video.wilson_score %></p> - <p id="rating"><%= translate(locale, "Rating: ") %><%= video.average_rating %> / 5</p> + <p id="rating"></p> <p id="engagement"><%= translate(locale, "Engagement: ") %><%= video.engagement %>%</p> <% if video.allowed_regions.size != REGIONS.size %> <p id="allowed_regions"> |
