diff options
| -rw-r--r-- | locales/ar.json | 7 | ||||
| -rw-r--r-- | locales/de.json | 7 | ||||
| -rw-r--r-- | locales/en-US.json | 7 | ||||
| -rw-r--r-- | locales/eu.json | 563 | ||||
| -rw-r--r-- | locales/fr.json | 7 | ||||
| -rw-r--r-- | locales/it.json | 7 | ||||
| -rw-r--r-- | locales/nb_NO.json | 563 | ||||
| -rw-r--r-- | locales/nl.json | 7 | ||||
| -rw-r--r-- | locales/pl.json | 563 | ||||
| -rw-r--r-- | locales/ru.json | 575 | ||||
| -rw-r--r-- | src/invidious.cr | 247 | ||||
| -rw-r--r-- | src/invidious/helpers/helpers.cr | 19 | ||||
| -rw-r--r-- | src/invidious/jobs.cr | 6 | ||||
| -rw-r--r-- | src/invidious/users.cr | 6 | ||||
| -rw-r--r-- | src/invidious/views/components/feed_menu.ecr | 10 | ||||
| -rw-r--r-- | src/invidious/views/index.ecr | 14 | ||||
| -rw-r--r-- | src/invidious/views/login.ecr | 56 | ||||
| -rw-r--r-- | src/invidious/views/popular.ecr | 2 | ||||
| -rw-r--r-- | src/invidious/views/preferences.ecr | 82 | ||||
| -rw-r--r-- | src/invidious/views/subscriptions.ecr | 2 | ||||
| -rw-r--r-- | src/invidious/views/template.ecr | 52 | ||||
| -rw-r--r-- | src/invidious/views/top.ecr | 2 | ||||
| -rw-r--r-- | src/invidious/views/trending.ecr | 2 |
23 files changed, 1486 insertions, 1320 deletions
diff --git a/locales/ar.json b/locales/ar.json index b2339cf0..adf85080 100644 --- a/locales/ar.json +++ b/locales/ar.json @@ -82,6 +82,13 @@ "Manage subscriptions": "إدارة المشتركين", "Watch history": "سجل المشاهدة", "Delete account": "حذف الحساب", + "Administrator preferences": "", + "Default homepage: ": "", + "Feed menu: ": "", + "Top enabled? ": "", + "CAPTCHA enabled? ": "", + "Login enabled? ": "", + "Registration enabled? ": "", "Save preferences": "حفظ التفضيلات", "Subscription manager": "مدير الإشتراكات", "`x` subscriptions": "`x` مشتركين", diff --git a/locales/de.json b/locales/de.json index d4ab6a52..1e2647e3 100644 --- a/locales/de.json +++ b/locales/de.json @@ -82,6 +82,13 @@ "Manage subscriptions": "Abonnements verwalten", "Watch history": "Verlauf", "Delete account": "Account löschen", + "Administrator preferences": "", + "Default homepage: ": "", + "Feed menu: ": "", + "Top enabled? ": "", + "CAPTCHA enabled? ": "", + "Login enabled? ": "", + "Registration enabled? ": "", "Save preferences": "Einstellungen speichern", "Subscription manager": "Abonnementverwaltung", "`x` subscriptions": "`x` Abonnements", diff --git a/locales/en-US.json b/locales/en-US.json index b985908e..d460238e 100644 --- a/locales/en-US.json +++ b/locales/en-US.json @@ -80,6 +80,13 @@ "Manage subscriptions": "Manage subscriptions", "Watch history": "Watch history", "Delete account": "Delete account", + "Administrator preferences": "Administrator preferences", + "Default homepage: ": "Default homepage: ", + "Feed menu: ": "Feed menu: ", + "Top enabled? ": "Top enabled? ", + "CAPTCHA enabled? ": "CAPTCHA enabled? ", + "Login enabled? ": "Login enabled? ", + "Registration enabled? ": "Registration enabled? ", "Save preferences": "Save preferences", "Subscription manager": "Subscription manager", "`x` subscriptions": "`x` subscriptions", diff --git a/locales/eu.json b/locales/eu.json index 901bb577..490245c7 100644 --- a/locales/eu.json +++ b/locales/eu.json @@ -1,280 +1,287 @@ { - "`x` subscribers": "`x` harpidedun", - "`x` videos": "`x` bideo", - "LIVE": "ZUZENEAN", - "Shared `x` ago": "Duela `x` partekatua", - "Unsubscribe": "Harpidetza kendu", - "Subscribe": "Harpidetu", - "Login to subscribe to `x`": "Saioa hasi `x`(e)ra harpidetzeko", - "View channel on YouTube": "Ikusi kanala YouTuben", - "newest": "berrienak", - "oldest": "zaharrenak", - "popular": "ospetsuenak", - "Preview page": "Aurrebista orria", - "Next page": "Hurrengo orria", - "Clear watch history?": "Garbitu ikusitakoen historia?", - "Yes": "Bai", - "No": "Ez", - "Import and Export Data": "Datuak inportatu eta esportatu", - "Import": "Inportatu", - "Import Invidious data": "Invidiouseko datuak inportatu", - "Import YouTube subscriptions": "YouTubeko harpidetzak inportatu", - "Import FreeTube subscriptions (.db)": "FreeTubeko harpidetzak inportatu (.db)", - "Import NewPipe subscriptions (.json)": "NewPipeko harpidetzak inportatu (.json)", - "Import NewPipe data (.zip)": "NewPipeko datuak inportatu (.zip)", - "Export": "Esportatu", - "Export subscriptions as OPML": "Esportatu harpidetzak OPML bezala", - "Export subscriptions as OPML (for NewPipe & FreeTube)": "Harpidetzak OPML bezala esportatu (NewPipe eta FreeTuberako)", - "Export data as JSON": "Datuak JSON bezala esportatu", - "Delete account?": "Kontua ezabatu?", - "History": "Historia", - "Previous page": "Aurreko orria", - "An alternative front-end to YouTube": "YouTuberako interfaze alternatibo bat", - "JavaScript license information": "JavaScript lizentzia informazioa", - "source": "iturburua", - "Login": "Saioa hasi", - "Login/Register": "Saioa hasi/Izena eman", - "Login to Google": "Googlekin hasi saioa", - "User ID:": "Erabiltzaile IDa:", - "Password:": "Pasahitza:", - "Time (h:mm:ss):": "Denbora (o:mm:ss):", - "Text CAPTCHA": "Testu CAPTCHA", - "Image CAPTCHA": "Irudi CAPTCHA", - "Sign In": "", - "Register": "", - "Email:": "", - "Google verification code:": "", - "Preferences": "", - "Player preferences": "", - "Always loop: ": "", - "Autoplay: ": "", - "Autoplay next video: ": "", - "Listen by default: ": "", - "Default speed: ": "", - "Preferred video quality: ": "", - "Player volume: ": "", - "Default comments: ": "", - "Default captions: ": "", - "Fallback captions: ": "", - "Show related videos? ": "", - "Visual preferences": "", - "Dark mode: ": "", - "Thin mode: ": "", - "Subscription preferences": "", - "Redirect homepage to feed: ": "", - "Number of videos shown in feed: ": "", - "Sort videos by: ": "", - "published": "", - "published - reverse": "", - "alphabetically": "", - "alphabetically - reverse": "", - "channel name": "", - "channel name - reverse": "", - "Only show latest video from channel: ": "", - "Only show latest unwatched video from channel: ": "", - "Only show unwatched: ": "", - "Only show notifications (if there are any): ": "", - "Data preferences": "", - "Clear watch history": "", - "Import/Export data": "", - "Manage subscriptions": "", - "Watch history": "", - "Delete account": "", - "Save preferences": "", - "Subscription manager": "", - "`x` subscriptions": "", - "Import/Export": "", - "unsubscribe": "", - "Subscriptions": "", - "`x` unseen notifications": "", - "search": "", - "Sign out": "", - "Released under the AGPLv3 by Omar Roth.": "", - "Source available here.": "", - "View JavaScript license information.": "", - "Trending": "", - "Watch video on Youtube": "", - "Genre: ": "", - "License: ": "", - "Family friendly? ": "", - "Wilson score: ": "", - "Engagement: ": "", - "Whitelisted regions: ": "", - "Blacklisted regions: ": "", - "Shared `x`": "", - "Hi! Looks like you have JavaScript disabled. Click here to view comments, keep in mind it may take a bit longer to load.": "", - "View YouTube comments": "", - "View more comments on Reddit": "", - "View `x` comments": "", - "View Reddit comments": "", - "Hide replies": "", - "Show replies": "", - "Incorrect password": "", - "Quota exceeded, try again in a few hours": "", - "Unable to login, make sure two-factor authentication (Authenticator or SMS) is enabled.": "", - "Invalid TFA code": "", - "Login failed. This may be because two-factor authentication is not enabled on your account.": "", - "Invalid answer": "", - "Invalid CAPTCHA": "", - "CAPTCHA is a required field": "", - "User ID is a required field": "", - "Password is a required field": "", - "Invalid username or password": "", - "Please sign in using 'Sign in with Google'": "", - "Password cannot be empty": "", - "Password cannot be longer than 55 characters": "", - "Please sign in": "", - "Invidious Private Feed for `x`": "", - "channel:`x`": "", - "Deleted or invalid channel": "", - "This channel does not exist.": "", - "Could not get channel info.": "", - "Could not fetch comments": "", - "View `x` replies": "", - "`x` ago": "", - "Load more": "", - "`x` points": "", - "Could not create mix.": "", - "Playlist is empty": "", - "Invalid playlist.": "", - "Playlist does not exist.": "", - "Could not pull trending pages.": "", - "Hidden field \"challenge\" is a required field": "", - "Hidden field \"token\" is a required field": "", - "Invalid challenge": "", - "Invalid token": "", - "Invalid user": "", - "Token is expired, please try again": "", - "English": "", - "English (auto-generated)": "", - "Afrikaans": "", - "Albanian": "", - "Amharic": "", - "Arabic": "", - "Armenian": "", - "Azerbaijani": "", - "Bangla": "", - "Basque": "", - "Belarusian": "", - "Bosnian": "", - "Bulgarian": "", - "Burmese": "", - "Catalan": "", - "Cebuano": "", - "Chinese (Simplified)": "", - "Chinese (Traditional)": "", - "Corsican": "", - "Croatian": "", - "Czech": "", - "Danish": "", - "Dutch": "", - "Esperanto": "", - "Estonian": "", - "Filipino": "", - "Finnish": "", - "French": "", - "Galician": "", - "Georgian": "", - "German": "", - "Greek": "", - "Gujarati": "", - "Haitian Creole": "", - "Hausa": "", - "Hawaiian": "", - "Hebrew": "", - "Hindi": "", - "Hmong": "", - "Hungarian": "", - "Icelandic": "", - "Igbo": "", - "Indonesian": "", - "Irish": "", - "Italian": "", - "Japanese": "", - "Javanese": "", - "Kannada": "", - "Kazakh": "", - "Khmer": "", - "Korean": "", - "Kurdish": "", - "Kyrgyz": "", - "Lao": "", - "Latin": "", - "Latvian": "", - "Lithuanian": "", - "Luxembourgish": "", - "Macedonian": "", - "Malagasy": "", - "Malay": "", - "Malayalam": "", - "Maltese": "", - "Maori": "", - "Marathi": "", - "Mongolian": "", - "Nepali": "", - "Norwegian": "", - "Nyanja": "", - "Pashto": "", - "Persian": "", - "Polish": "", - "Portuguese": "", - "Punjabi": "", - "Romanian": "", - "Russian": "", - "Samoan": "", - "Scottish Gaelic": "", - "Serbian": "", - "Shona": "", - "Sindhi": "", - "Sinhala": "", - "Slovak": "", - "Slovenian": "", - "Somali": "", - "Southern Sotho": "", - "Spanish": "", - "Spanish (Latin America)": "", - "Sundanese": "", - "Swahili": "", - "Swedish": "", - "Tajik": "", - "Tamil": "", - "Telugu": "", - "Thai": "", - "Turkish": "", - "Ukrainian": "", - "Urdu": "", - "Uzbek": "", - "Vietnamese": "", - "Welsh": "", - "Western Frisian": "", - "Xhosa": "", - "Yiddish": "", - "Yoruba": "", - "Zulu": "", - "`x` years": "", - "`x` months": "", - "`x` weeks": "", - "`x` days": "", - "`x` hours": "", - "`x` minutes": "", - "`x` seconds": "", - "Fallback comments: ": "", - "Popular": "", - "Top": "", - "About": "", - "Rating: ": "", - "Language: ": "", - "Default": "", - "Music": "", - "Gaming": "", - "News": "", - "Movies": "", - "Download": "", - "Download as: ": "", - "%A %B %-d, %Y": "", - "(edited)": "", - "Youtube permalink of the comment": "", - "`x` marked it with a ❤": "", - "Audio mode": "", - "Video mode": "" + "`x` subscribers": "`x` harpidedun", + "`x` videos": "`x` bideo", + "LIVE": "ZUZENEAN", + "Shared `x` ago": "Duela `x` partekatua", + "Unsubscribe": "Harpidetza kendu", + "Subscribe": "Harpidetu", + "Login to subscribe to `x`": "Saioa hasi `x`(e)ra harpidetzeko", + "View channel on YouTube": "Ikusi kanala YouTuben", + "newest": "berrienak", + "oldest": "zaharrenak", + "popular": "ospetsuenak", + "Preview page": "Aurrebista orria", + "Next page": "Hurrengo orria", + "Clear watch history?": "Garbitu ikusitakoen historia?", + "Yes": "Bai", + "No": "Ez", + "Import and Export Data": "Datuak inportatu eta esportatu", + "Import": "Inportatu", + "Import Invidious data": "Invidiouseko datuak inportatu", + "Import YouTube subscriptions": "YouTubeko harpidetzak inportatu", + "Import FreeTube subscriptions (.db)": "FreeTubeko harpidetzak inportatu (.db)", + "Import NewPipe subscriptions (.json)": "NewPipeko harpidetzak inportatu (.json)", + "Import NewPipe data (.zip)": "NewPipeko datuak inportatu (.zip)", + "Export": "Esportatu", + "Export subscriptions as OPML": "Esportatu harpidetzak OPML bezala", + "Export subscriptions as OPML (for NewPipe & FreeTube)": "Harpidetzak OPML bezala esportatu (NewPipe eta FreeTuberako)", + "Export data as JSON": "Datuak JSON bezala esportatu", + "Delete account?": "Kontua ezabatu?", + "History": "Historia", + "Previous page": "Aurreko orria", + "An alternative front-end to YouTube": "YouTuberako interfaze alternatibo bat", + "JavaScript license information": "JavaScript lizentzia informazioa", + "source": "iturburua", + "Login": "Saioa hasi", + "Login/Register": "Saioa hasi/Izena eman", + "Login to Google": "Googlekin hasi saioa", + "User ID:": "Erabiltzaile IDa:", + "Password:": "Pasahitza:", + "Time (h:mm:ss):": "Denbora (o:mm:ss):", + "Text CAPTCHA": "Testu CAPTCHA", + "Image CAPTCHA": "Irudi CAPTCHA", + "Sign In": "", + "Register": "", + "Email:": "", + "Google verification code:": "", + "Preferences": "", + "Player preferences": "", + "Always loop: ": "", + "Autoplay: ": "", + "Autoplay next video: ": "", + "Listen by default: ": "", + "Default speed: ": "", + "Preferred video quality: ": "", + "Player volume: ": "", + "Default comments: ": "", + "Default captions: ": "", + "Fallback captions: ": "", + "Show related videos? ": "", + "Visual preferences": "", + "Dark mode: ": "", + "Thin mode: ": "", + "Subscription preferences": "", + "Redirect homepage to feed: ": "", + "Number of videos shown in feed: ": "", + "Sort videos by: ": "", + "published": "", + "published - reverse": "", + "alphabetically": "", + "alphabetically - reverse": "", + "channel name": "", + "channel name - reverse": "", + "Only show latest video from channel: ": "", + "Only show latest unwatched video from channel: ": "", + "Only show unwatched: ": "", + "Only show notifications (if there are any): ": "", + "Data preferences": "", + "Clear watch history": "", + "Import/Export data": "", + "Manage subscriptions": "", + "Watch history": "", + "Delete account": "", + "Administrator preferences": "", + "Default homepage: ": "", + "Feed menu: ": "", + "Top enabled? ": "", + "CAPTCHA enabled? ": "", + "Login enabled? ": "", + "Registration enabled? ": "", + "Save preferences": "", + "Subscription manager": "", + "`x` subscriptions": "", + "Import/Export": "", + "unsubscribe": "", + "Subscriptions": "", + "`x` unseen notifications": "", + "search": "", + "Sign out": "", + "Released under the AGPLv3 by Omar Roth.": "", + "Source available here.": "", + "View JavaScript license information.": "", + "Trending": "", + "Watch video on Youtube": "", + "Genre: ": "", + "License: ": "", + "Family friendly? ": "", + "Wilson score: ": "", + "Engagement: ": "", + "Whitelisted regions: ": "", + "Blacklisted regions: ": "", + "Shared `x`": "", + "Hi! Looks like you have JavaScript disabled. Click here to view comments, keep in mind it may take a bit longer to load.": "", + "View YouTube comments": "", + "View more comments on Reddit": "", + "View `x` comments": "", + "View Reddit comments": "", + "Hide replies": "", + "Show replies": "", + "Incorrect password": "", + "Quota exceeded, try again in a few hours": "", + "Unable to login, make sure two-factor authentication (Authenticator or SMS) is enabled.": "", + "Invalid TFA code": "", + "Login failed. This may be because two-factor authentication is not enabled on your account.": "", + "Invalid answer": "", + "Invalid CAPTCHA": "", + "CAPTCHA is a required field": "", + "User ID is a required field": "", + "Password is a required field": "", + "Invalid username or password": "", + "Please sign in using 'Sign in with Google'": "", + "Password cannot be empty": "", + "Password cannot be longer than 55 characters": "", + "Please sign in": "", + "Invidious Private Feed for `x`": "", + "channel:`x`": "", + "Deleted or invalid channel": "", + "This channel does not exist.": "", + "Could not get channel info.": "", + "Could not fetch comments": "", + "View `x` replies": "", + "`x` ago": "", + "Load more": "", + "`x` points": "", + "Could not create mix.": "", + "Playlist is empty": "", + "Invalid playlist.": "", + "Playlist does not exist.": "", + "Could not pull trending pages.": "", + "Hidden field \"challenge\" is a required field": "", + "Hidden field \"token\" is a required field": "", + "Invalid challenge": "", + "Invalid token": "", + "Invalid user": "", + "Token is expired, please try again": "", + "English": "", + "English (auto-generated)": "", + "Afrikaans": "", + "Albanian": "", + "Amharic": "", + "Arabic": "", + "Armenian": "", + "Azerbaijani": "", + "Bangla": "", + "Basque": "", + "Belarusian": "", + "Bosnian": "", + "Bulgarian": "", + "Burmese": "", + "Catalan": "", + "Cebuano": "", + "Chinese (Simplified)": "", + "Chinese (Traditional)": "", + "Corsican": "", + "Croatian": "", + "Czech": "", + "Danish": "", + "Dutch": "", + "Esperanto": "", + "Estonian": "", + "Filipino": "", + "Finnish": "", + "French": "", + "Galician": "", + "Georgian": "", + "German": "", + "Greek": "", + "Gujarati": "", + "Haitian Creole": "", + "Hausa": "", + "Hawaiian": "", + "Hebrew": "", + "Hindi": "", + "Hmong": "", + "Hungarian": "", + "Icelandic": "", + "Igbo": "", + "Indonesian": "", + "Irish": "", + "Italian": "", + "Japanese": "", + "Javanese": "", + "Kannada": "", + "Kazakh": "", + "Khmer": "", + "Korean": "", + "Kurdish": "", + "Kyrgyz": "", + "Lao": "", + "Latin": "", + "Latvian": "", + "Lithuanian": "", + "Luxembourgish": "", + "Macedonian": "", + "Malagasy": "", + "Malay": "", + "Malayalam": "", + "Maltese": "", + "Maori": "", + "Marathi": "", + "Mongolian": "", + "Nepali": "", + "Norwegian": "", + "Nyanja": "", + "Pashto": "", + "Persian": "", + "Polish": "", + "Portuguese": "", + "Punjabi": "", + "Romanian": "", + "Russian": "", + "Samoan": "", + "Scottish Gaelic": "", + "Serbian": "", + "Shona": "", + "Sindhi": "", + "Sinhala": "", + "Slovak": "", + "Slovenian": "", + "Somali": "", + "Southern Sotho": "", + "Spanish": "", + "Spanish (Latin America)": "", + "Sundanese": "", + "Swahili": "", + "Swedish": "", + "Tajik": "", + "Tamil": "", + "Telugu": "", + "Thai": "", + "Turkish": "", + "Ukrainian": "", + "Urdu": "", + "Uzbek": "", + "Vietnamese": "", + "Welsh": "", + "Western Frisian": "", + "Xhosa": "", + "Yiddish": "", + "Yoruba": "", + "Zulu": "", + "`x` years": "", + "`x` months": "", + "`x` weeks": "", + "`x` days": "", + "`x` hours": "", + "`x` minutes": "", + "`x` seconds": "", + "Fallback comments: ": "", + "Popular": "", + "Top": "", + "About": "", + "Rating: ": "", + "Language: ": "", + "Default": "", + "Music": "", + "Gaming": "", + "News": "", + "Movies": "", + "Download": "", + "Download as: ": "", + "%A %B %-d, %Y": "", + "(edited)": "", + "Youtube permalink of the comment": "", + "`x` marked it with a ❤": "", + "Audio mode": "", + "Video mode": "" } diff --git a/locales/fr.json b/locales/fr.json index 71935f36..ec6508b9 100644 --- a/locales/fr.json +++ b/locales/fr.json @@ -79,6 +79,13 @@ "Manage subscriptions": "Gérer les abonnements", "Watch history": "Historique de visionnage", "Delete account": "Supprimer votre compte", + "Administrator preferences": "", + "Default homepage: ": "", + "Feed menu: ": "", + "Top enabled? ": "", + "CAPTCHA enabled? ": "", + "Login enabled? ": "", + "Registration enabled? ": "", "Save preferences": "Enregistrer les préférences", "Subscription manager": "Gestionnaire d'abonnement", "`x` subscriptions": "`x` abonnements", diff --git a/locales/it.json b/locales/it.json index 403c018c..8b47c026 100644 --- a/locales/it.json +++ b/locales/it.json @@ -79,6 +79,13 @@ "Manage subscriptions": "Gestisci le iscrizioni", "Watch history": "Cronologia dei video", "Delete account": "Elimina l'account", + "Administrator preferences": "", + "Default homepage: ": "", + "Feed menu: ": "", + "Top enabled? ": "", + "CAPTCHA enabled? ": "", + "Login enabled? ": "", + "Registration enabled? ": "", "Save preferences": "Salva le preferenze", "Subscription manager": "Gestisci le iscrizioni", "`x` subscriptions": "`x` iscrizioni", diff --git a/locales/nb_NO.json b/locales/nb_NO.json index 5dbd3e8f..6a277741 100644 --- a/locales/nb_NO.json +++ b/locales/nb_NO.json @@ -1,280 +1,287 @@ { - "`x` subscribers": "`x` abonnenter", - "`x` videos": "`x` videoer", - "LIVE": "SANNTIDSVISNING", - "Shared `x` ago": "Delt for `x` siden", - "Unsubscribe": "Opphev abonnement", - "Subscribe": "Abonner", - "Login to subscribe to `x`": "Logg inn for å abonnere på `x`", - "View channel on YouTube": "Vis kanal på YouTube", - "newest": "nyeste", - "oldest": "eldste", - "popular": "populært", - "Preview page": "Forhåndsvis side", - "Next page": "Neste side", - "Clear watch history?": "Tøm visningshistorikk?", - "Yes": "Ja", - "No": "Nei", - "Import and Export Data": "Importer- og eksporter data", - "Import": "Importer", - "Import Invidious data": "Importer Invidious-data", - "Import YouTube subscriptions": "Importer YouTube-abonnenter", - "Import FreeTube subscriptions (.db)": "Importer FreeTube-abonnenter (.db)", - "Import NewPipe subscriptions (.json)": "Importer NewPipe-abonnenter (.json)", - "Import NewPipe data (.zip)": "Importer NewPipe-data (.zip)", - "Export": "Eksporter", - "Export subscriptions as OPML": "Eksporter abonnenter som OPML", - "Export subscriptions as OPML (for NewPipe & FreeTube)": "Eksporter abonnenter som OPML (for NewPipe og FreeTube)", - "Export data as JSON": "Eksporter data som JSON", - "Delete account?": "Slett konto?", - "History": "Historikk", - "Previous page": "Forrige side", - "An alternative front-end to YouTube": "En alternativ grenseflate for YouTube", - "JavaScript license information": "JavaScript-lisensinformasjon", - "source": "kilde", - "Login": "Logg inn", - "Login/Register": "Logg inn/registrer", - "Login to Google": "Logg inn med Google", - "User ID:": "Bruker-ID:", - "Password:": "Passord:", - "Time (h:mm:ss):": "Tid (h:mm:ss):", - "Text CAPTCHA": "Tekst-CAPTCHA", - "Image CAPTCHA": "Bilde-CAPTCHA", - "Sign In": "Innlogging", - "Register": "Registrer", - "Email:": "E-post:", - "Google verification code:": "Google-bekreftelseskode:", - "Preferences": "Innstillinger", - "Player preferences": "Avspillerinnstillinger", - "Always loop: ": "Alltid gjenta: ", - "Autoplay: ": "Autoavspilling: ", - "Autoplay next video: ": "Autospill neste video: ", - "Listen by default: ": "Lytt som forvalg: ", - "Default speed: ": "Forvalgt hastighet: ", - "Preferred video quality: ": "Foretrukket videokvalitet: ", - "Player volume: ": "Avspillerlydstyrke: ", - "Default comments: ": "Forvalgte kommentarer: ", - "Default captions: ": "Forvalgte undertitler: ", - "Fallback captions: ": "Tilbakefallsundertitler: ", - "Show related videos? ": "Vis relaterte videoer? ", - "Visual preferences": "Visuelle innstillinger", - "Dark mode: ": "Mørk drakt: ", - "Thin mode: ": "Tynt modus: ", - "Subscription preferences": "Abonnementsinnstillinger", - "Redirect homepage to feed: ": "Videresend hjemmeside til flyt: ", - "Number of videos shown in feed: ": "Antall videoer å vise i flyt: ", - "Sort videos by: ": "Sorter videoer etter: ", - "published": "publisert", - "published - reverse": "publisert - motsatt", - "alphabetically": "alfabetisk", - "alphabetically - reverse": "alfabetisk - motsatt", - "channel name": "kanalnavn", - "channel name - reverse": "kanalnavn - motsatt", - "Only show latest video from channel: ": "Kun vis siste video fra kanal: ", - "Only show latest unwatched video from channel: ": "Kun vis siste usette video fra kanal: ", - "Only show unwatched: ": "Kun vis usette: ", - "Only show notifications (if there are any): ": "Kun vis merknader (hvis det er noen): ", - "Data preferences": "Datainnstillinger", - "Clear watch history": "Tøm visningshistorikk", - "Import/Export data": "Importer/eksporter data", - "Manage subscriptions": "Behandle abonnementer", - "Watch history": "Visningshistorikk", - "Delete account": "Slett konto", - "Save preferences": "Lagre innstillinger", - "Subscription manager": "Abonnementsbehandler", - "`x` subscriptions": "`x` abonnementer", - "Import/Export": "Importer/eksporter", - "unsubscribe": "opphev abonnement", - "Subscriptions": "Abonnement", - "`x` unseen notifications": "`x` usette merknader", - "search": "søk", - "Sign out": "Logg ut", - "Released under the AGPLv3 by Omar Roth.": "Utgitt med AGPLv3+lisens av Omar Roth.", - "Source available here.": "Kildekode tilgjengelig her.", - "View JavaScript license information.": "Vis JavaScript-lisensinfo.", - "Trending": "Trendsettende", - "Watch video on Youtube": "Vis video på YouTube", - "Genre: ": "Sjanger: ", - "License: ": "Lisens: ", - "Family friendly? ": "Familievennlig? ", - "Wilson score: ": "Wilson-poengsum: ", - "Engagement: ": "Engasjement: ", - "Whitelisted regions: ": "Hvitlistede regioner: ", - "Blacklisted regions: ": "Svartelistede regioner: ", - "Shared `x`": "Delt `x`", - "Hi! Looks like you have JavaScript disabled. Click here to view comments, keep in mind it may take a bit longer to load.": "Hei. Det ser ut til at du har JavaScript avslått. Klikk her for å vise kommentarer, ha i minnet at innlasting tar lengre tid.", - "View YouTube comments": "Vis YouTube-kommentarer", - "View more comments on Reddit": "Vis flere kommenterer på Reddit", - "View `x` comments": "Vis `x` kommentarer", - "View Reddit comments": "Vis Reddit-kommentarer", - "Hide replies": "Skjul svar", - "Show replies": "Vis svar", - "Incorrect password": "Feil passord", - "Quota exceeded, try again in a few hours": "Kvote overskredet, prøv igjen om et par timer", - "Unable to login, make sure two-factor authentication (Authenticator or SMS) is enabled.": "Kunne ikke logge inn, forsikre deg om at tofaktor-identitetsbekreftelse (Authenticator eller SMS) er skrudd på.", - "Invalid TFA code": "Ugyldig tofaktorkode", - "Login failed. This may be because two-factor authentication is not enabled on your account.": "Innlogging mislyktes. Dette kan være fordi tofaktor-identitetsbekreftelse er skrudd av på kontoen din.", - "Invalid answer": "Ugyldig svar", - "Invalid CAPTCHA": "Ugyldig CAPTCHA", - "CAPTCHA is a required field": "CAPTCHA er et påkrevd felt", - "User ID is a required field": "Bruker-ID er et påkrevd felt", - "Password is a required field": "Passord er et påkrevd felt", - "Invalid username or password": "Ugyldig brukernavn eller passord", - "Please sign in using 'Sign in with Google'": "Logg inn ved bruk av \"Google-innlogging\"", - "Password cannot be empty": "Passordet kan ikke være tomt", - "Password cannot be longer than 55 characters": "Passordet kan ikke være lengre enn 55 tegn", - "Please sign in": "Logg inn", - "Invidious Private Feed for `x`": "Ugyldig privat flyt for `x`", - "channel:`x`": "kanal `x`", - "Deleted or invalid channel": "Slettet eller ugyldig kanal", - "This channel does not exist.": "Denne kanalen finnes ikke.", - "Could not get channel info.": "Kunne ikke innhente kanalinfo.", - "Could not fetch comments": "Kunne ikke hente kommentarer", - "View `x` replies": "Vis `x` svar", - "`x` ago": "`x` siden", - "Load more": "Last inn flere", - "`x` points": "`x` poeng", - "Could not create mix.": "Kunne ikke opprette miks.", - "Playlist is empty": "Spillelisten er tom", - "Invalid playlist.": "Ugyldig spilleliste.", - "Playlist does not exist.": "Spillelisten finnes ikke.", - "Could not pull trending pages.": "Kunne ikke hente trendsettende sider.", - "Hidden field \"challenge\" is a required field": "Skjult felt \"utfordring\" er et påkrevd felt", - "Hidden field \"token\" is a required field": "Skjult felt \"symbol\" er et påkrevd felt", - "Invalid challenge": "Ugyldig utfordring", - "Invalid token": "Ugyldig symbol", - "Invalid user": "Ugyldig bruker", - "Token is expired, please try again": "Symbol utløpt, prøv igjen", - "English": "Engelsk", - "English (auto-generated)": "Engelsk (auto-generert)", - "Afrikaans": "", - "Albanian": "Albansk", - "Amharic": "", - "Arabic": "Arabisk", - "Armenian": "Armensk", - "Azerbaijani": "", - "Bangla": "", - "Basque": "", - "Belarusian": "Hviterussisk", - "Bosnian": "Bosnisk", - "Bulgarian": "Bulgarsk", - "Burmese": "Burmesisk", - "Catalan": "Katalansk", - "Cebuano": "", - "Chinese (Simplified)": "", - "Chinese (Traditional)": "", - "Corsican": "", - "Croatian": "", - "Czech": "Tsjekkisk", - "Danish": "Dansk", - "Dutch": "", - "Esperanto": "Esperanto", - "Estonian": "", - "Filipino": "", - "Finnish": "Finsk", - "French": "Fransk", - "Galician": "", - "Georgian": "", - "German": "", - "Greek": "", - "Gujarati": "", - "Haitian Creole": "", - "Hausa": "", - "Hawaiian": "", - "Hebrew": "", - "Hindi": "", - "Hmong": "", - "Hungarian": "Ungarsk", - "Icelandic": "Islandsk", - "Igbo": "", - "Indonesian": "Indonesisk", - "Irish": "Irsk", - "Italian": "Italiensk", - "Japanese": "Japansk", - "Javanese": "", - "Kannada": "", - "Kazakh": "", - "Khmer": "", - "Korean": "", - "Kurdish": "", - "Kyrgyz": "", - "Lao": "", - "Latin": "", - "Latvian": "", - "Lithuanian": "", - "Luxembourgish": "", - "Macedonian": "", - "Malagasy": "", - "Malay": "", - "Malayalam": "", - "Maltese": "", - "Maori": "", - "Marathi": "", - "Mongolian": "", - "Nepali": "", - "Norwegian": "Norsk bokmål", - "Nyanja": "", - "Pashto": "", - "Persian": "", - "Polish": "", - "Portuguese": "", - "Punjabi": "", - "Romanian": "", - "Russian": "Russisk", - "Samoan": "", - "Scottish Gaelic": "", - "Serbian": "Serbisk", - "Shona": "", - "Sindhi": "", - "Sinhala": "", - "Slovak": "Slovakisk", - "Slovenian": "Slovensk", - "Somali": "Somali", - "Southern Sotho": "", - "Spanish": "Spansk", - "Spanish (Latin America)": "", - "Sundanese": "", - "Swahili": "", - "Swedish": "Svensk", - "Tajik": "", - "Tamil": "", - "Telugu": "", - "Thai": "", - "Turkish": "Tyrkisk", - "Ukrainian": "Ukrainsk", - "Urdu": "", - "Uzbek": "", - "Vietnamese": "Vietnamesisk", - "Welsh": "", - "Western Frisian": "", - "Xhosa": "", - "Yiddish": "", - "Yoruba": "", - "Zulu": "", - "`x` years": "`x` år", - "`x` months": "`x` måneder", - "`x` weeks": "`x` uker", - "`x` days": "`x` dager", - "`x` hours": "`x` timer", - "`x` minutes": "`x` minutter", - "`x` seconds": "`x` sekunder", - "Fallback comments: ": "Tilbakefallskommentarer: ", - "Popular": "Pupulært", - "Top": "Topp", - "About": "Om", - "Rating: ": "Vurdering: ", - "Language: ": "Språk: ", - "Default": "Forvalg", - "Music": "Musikk", - "Gaming": "Spill", - "News": "Nyheter", - "Movies": "Filmer", - "Download": "Last ned", - "Download as: ": "Last ned som: ", - "%A %B %-d, %Y": "", - "(edited)": "(redigert)", - "Youtube permalink of the comment": "Permanent YouTube-lenke til innholdet", - "`x` marked it with a ❤": "`x` levnet et ❤", - "Audio mode": "Lydmodus", - "Video mode": "Video-modus" + "`x` subscribers": "`x` abonnenter", + "`x` videos": "`x` videoer", + "LIVE": "SANNTIDSVISNING", + "Shared `x` ago": "Delt for `x` siden", + "Unsubscribe": "Opphev abonnement", + "Subscribe": "Abonner", + "Login to subscribe to `x`": "Logg inn for å abonnere på `x`", + "View channel on YouTube": "Vis kanal på YouTube", + "newest": "nyeste", + "oldest": "eldste", + "popular": "populært", + "Preview page": "Forhåndsvis side", + "Next page": "Neste side", + "Clear watch history?": "Tøm visningshistorikk?", + "Yes": "Ja", + "No": "Nei", + "Import and Export Data": "Importer- og eksporter data", + "Import": "Importer", + "Import Invidious data": "Importer Invidious-data", + "Import YouTube subscriptions": "Importer YouTube-abonnenter", + "Import FreeTube subscriptions (.db)": "Importer FreeTube-abonnenter (.db)", + "Import NewPipe subscriptions (.json)": "Importer NewPipe-abonnenter (.json)", + "Import NewPipe data (.zip)": "Importer NewPipe-data (.zip)", + "Export": "Eksporter", + "Export subscriptions as OPML": "Eksporter abonnenter som OPML", + "Export subscriptions as OPML (for NewPipe & FreeTube)": "Eksporter abonnenter som OPML (for NewPipe og FreeTube)", + "Export data as JSON": "Eksporter data som JSON", + "Delete account?": "Slett konto?", + "History": "Historikk", + "Previous page": "Forrige side", + "An alternative front-end to YouTube": "En alternativ grenseflate for YouTube", + "JavaScript license information": "JavaScript-lisensinformasjon", + "source": "kilde", + "Login": "Logg inn", + "Login/Register": "Logg inn/registrer", + "Login to Google": "Logg inn med Google", + "User ID:": "Bruker-ID:", + "Password:": "Passord:", + "Time (h:mm:ss):": "Tid (h:mm:ss):", + "Text CAPTCHA": "Tekst-CAPTCHA", + "Image CAPTCHA": "Bilde-CAPTCHA", + "Sign In": "Innlogging", + "Register": "Registrer", + "Email:": "E-post:", + "Google verification code:": "Google-bekreftelseskode:", + "Preferences": "Innstillinger", + "Player preferences": "Avspillerinnstillinger", + "Always loop: ": "Alltid gjenta: ", + "Autoplay: ": "Autoavspilling: ", + "Autoplay next video: ": "Autospill neste video: ", + "Listen by default: ": "Lytt som forvalg: ", + "Default speed: ": "Forvalgt hastighet: ", + "Preferred video quality: ": "Foretrukket videokvalitet: ", + "Player volume: ": "Avspillerlydstyrke: ", + "Default comments: ": "Forvalgte kommentarer: ", + "Default captions: ": "Forvalgte undertitler: ", + "Fallback captions: ": "Tilbakefallsundertitler: ", + "Show related videos? ": "Vis relaterte videoer? ", + "Visual preferences": "Visuelle innstillinger", + "Dark mode: ": "Mørk drakt: ", + "Thin mode: ": "Tynt modus: ", + "Subscription preferences": "Abonnementsinnstillinger", + "Redirect homepage to feed: ": "Videresend hjemmeside til flyt: ", + "Number of videos shown in feed: ": "Antall videoer å vise i flyt: ", + "Sort videos by: ": "Sorter videoer etter: ", + "published": "publisert", + "published - reverse": "publisert - motsatt", + "alphabetically": "alfabetisk", + "alphabetically - reverse": "alfabetisk - motsatt", + "channel name": "kanalnavn", + "channel name - reverse": "kanalnavn - motsatt", + "Only show latest video from channel: ": "Kun vis siste video fra kanal: ", + "Only show latest unwatched video from channel: ": "Kun vis siste usette video fra kanal: ", + "Only show unwatched: ": "Kun vis usette: ", + "Only show notifications (if there are any): ": "Kun vis merknader (hvis det er noen): ", + "Data preferences": "Datainnstillinger", + "Clear watch history": "Tøm visningshistorikk", + "Import/Export data": "Importer/eksporter data", + "Manage subscriptions": "Behandle abonnementer", + "Watch history": "Visningshistorikk", + "Delete account": "Slett konto", + "Administrator preferences": "", + "Default homepage: ": "", + "Feed menu: ": "", + "Top enabled? ": "", + "CAPTCHA enabled? ": "", + "Login enabled? ": "", + "Registration enabled? ": "", + "Save preferences": "Lagre innstillinger", + "Subscription manager": "Abonnementsbehandler", + "`x` subscriptions": "`x` abonnementer", + "Import/Export": "Importer/eksporter", + "unsubscribe": "opphev abonnement", + "Subscriptions": "Abonnement", + "`x` unseen notifications": "`x` usette merknader", + "search": "søk", + "Sign out": "Logg ut", + "Released under the AGPLv3 by Omar Roth.": "Utgitt med AGPLv3+lisens av Omar Roth.", + "Source available here.": "Kildekode tilgjengelig her.", + "View JavaScript license information.": "Vis JavaScript-lisensinfo.", + "Trending": "Trendsettende", + "Watch video on Youtube": "Vis video på YouTube", + "Genre: ": "Sjanger: ", + "License: ": "Lisens: ", + "Family friendly? ": "Familievennlig? ", + "Wilson score: ": "Wilson-poengsum: ", + "Engagement: ": "Engasjement: ", + "Whitelisted regions: ": "Hvitlistede regioner: ", + "Blacklisted regions: ": "Svartelistede regioner: ", + "Shared `x`": "Delt `x`", + "Hi! Looks like you have JavaScript disabled. Click here to view comments, keep in mind it may take a bit longer to load.": "Hei. Det ser ut til at du har JavaScript avslått. Klikk her for å vise kommentarer, ha i minnet at innlasting tar lengre tid.", + "View YouTube comments": "Vis YouTube-kommentarer", + "View more comments on Reddit": "Vis flere kommenterer på Reddit", + "View `x` comments": "Vis `x` kommentarer", + "View Reddit comments": "Vis Reddit-kommentarer", + "Hide replies": "Skjul svar", + "Show replies": "Vis svar", + "Incorrect password": "Feil passord", + "Quota exceeded, try again in a few hours": "Kvote overskredet, prøv igjen om et par timer", + "Unable to login, make sure two-factor authentication (Authenticator or SMS) is enabled.": "Kunne ikke logge inn, forsikre deg om at tofaktor-identitetsbekreftelse (Authenticator eller SMS) er skrudd på.", + "Invalid TFA code": "Ugyldig tofaktorkode", + "Login failed. This may be because two-factor authentication is not enabled on your account.": "Innlogging mislyktes. Dette kan være fordi tofaktor-identitetsbekreftelse er skrudd av på kontoen din.", + "Invalid answer": "Ugyldig svar", + "Invalid CAPTCHA": "Ugyldig CAPTCHA", + "CAPTCHA is a required field": "CAPTCHA er et påkrevd felt", + "User ID is a required field": "Bruker-ID er et påkrevd felt", + "Password is a required field": "Passord er et påkrevd felt", + "Invalid username or password": "Ugyldig brukernavn eller passord", + "Please sign in using 'Sign in with Google'": "Logg inn ved bruk av \"Google-innlogging\"", + "Password cannot be empty": "Passordet kan ikke være tomt", + "Password cannot be longer than 55 characters": "Passordet kan ikke være lengre enn 55 tegn", + "Please sign in": "Logg inn", + "Invidious Private Feed for `x`": "Ugyldig privat flyt for `x`", + "channel:`x`": "kanal `x`", + "Deleted or invalid channel": "Slettet eller ugyldig kanal", + "This channel does not exist.": "Denne kanalen finnes ikke.", + "Could not get channel info.": "Kunne ikke innhente kanalinfo.", + "Could not fetch comments": "Kunne ikke hente kommentarer", + "View `x` replies": "Vis `x` svar", + "`x` ago": "`x` siden", + "Load more": "Last inn flere", + "`x` points": "`x` poeng", + "Could not create mix.": "Kunne ikke opprette miks.", + "Playlist is empty": "Spillelisten er tom", + "Invalid playlist.": "Ugyldig spilleliste.", + "Playlist does not exist.": "Spillelisten finnes ikke.", + "Could not pull trending pages.": "Kunne ikke hente trendsettende sider.", + "Hidden field \"challenge\" is a required field": "Skjult felt \"utfordring\" er et påkrevd felt", + "Hidden field \"token\" is a required field": "Skjult felt \"symbol\" er et påkrevd felt", + "Invalid challenge": "Ugyldig utfordring", + "Invalid token": "Ugyldig symbol", + "Invalid user": "Ugyldig bruker", + "Token is expired, please try again": "Symbol utløpt, prøv igjen", + "English": "Engelsk", + "English (auto-generated)": "Engelsk (auto-generert)", + "Afrikaans": "", + "Albanian": "Albansk", + "Amharic": "", + "Arabic": "Arabisk", + "Armenian": "Armensk", + "Azerbaijani": "", + "Bangla": "", + "Basque": "", + "Belarusian": "Hviterussisk", + "Bosnian": "Bosnisk", + "Bulgarian": "Bulgarsk", + "Burmese": "Burmesisk", + "Catalan": "Katalansk", + "Cebuano": "", + "Chinese (Simplified)": "", + "Chinese (Traditional)": "", + "Corsican": "", + "Croatian": "", + "Czech": "Tsjekkisk", + "Danish": "Dansk", + "Dutch": "", + "Esperanto": "Esperanto", + "Estonian": "", + "Filipino": "", + "Finnish": "Finsk", + "French": "Fransk", + "Galician": "", + "Georgian": "", + "German": "", + "Greek": "", + "Gujarati": "", + "Haitian Creole": "", + "Hausa": "", + "Hawaiian": "", + "Hebrew": "", + "Hindi": "", + "Hmong": "", + "Hungarian": "Ungarsk", + "Icelandic": "Islandsk", + "Igbo": "", + "Indonesian": "Indonesisk", + "Irish": "Irsk", + "Italian": "Italiensk", + "Japanese": "Japansk", + "Javanese": "", + "Kannada": "", + "Kazakh": "", + "Khmer": "", + "Korean": "", + "Kurdish": "", + "Kyrgyz": "", + "Lao": "", + "Latin": "", + "Latvian": "", + "Lithuanian": "", + "Luxembourgish": "", + "Macedonian": "", + "Malagasy": "", + "Malay": "", + "Malayalam": "", + "Maltese": "", + "Maori": "", + "Marathi": "", + "Mongolian": "", + "Nepali": "", + "Norwegian": "Norsk bokmål", + "Nyanja": "", + "Pashto": "", + "Persian": "", + "Polish": "", + "Portuguese": "", + "Punjabi": "", + "Romanian": "", + "Russian": "Russisk", + "Samoan": "", + "Scottish Gaelic": "", + "Serbian": "Serbisk", + "Shona": "", + "Sindhi": "", + "Sinhala": "", + "Slovak": "Slovakisk", + "Slovenian": "Slovensk", + "Somali": "Somali", + "Southern Sotho": "", + "Spanish": "Spansk", + "Spanish (Latin America)": "", + "Sundanese": "", + "Swahili": "", + "Swedish": "Svensk", + "Tajik": "", + "Tamil": "", + "Telugu": "", + "Thai": "", + "Turkish": "Tyrkisk", + "Ukrainian": "Ukrainsk", + "Urdu": "", + "Uzbek": "", + "Vietnamese": "Vietnamesisk", + "Welsh": "", + "Western Frisian": "", + "Xhosa": "", + "Yiddish": "", + "Yoruba": "", + "Zulu": "", + "`x` years": "`x` år", + "`x` months": "`x` måneder", + "`x` weeks": "`x` uker", + "`x` days": "`x` dager", + "`x` hours": "`x` timer", + "`x` minutes": "`x` minutter", + "`x` seconds": "`x` sekunder", + "Fallback comments: ": "Tilbakefallskommentarer: ", + "Popular": "Pupulært", + "Top": "Topp", + "About": "Om", + "Rating: ": "Vurdering: ", + "Language: ": "Språk: ", + "Default": "Forvalg", + "Music": "Musikk", + "Gaming": "Spill", + "News": "Nyheter", + "Movies": "Filmer", + "Download": "Last ned", + "Download as: ": "Last ned som: ", + "%A %B %-d, %Y": "", + "(edited)": "(redigert)", + "Youtube permalink of the comment": "Permanent YouTube-lenke til innholdet", + "`x` marked it with a ❤": "`x` levnet et ❤", + "Audio mode": "Lydmodus", + "Video mode": "Video-modus" } diff --git a/locales/nl.json b/locales/nl.json index 1f2e3930..04937de9 100644 --- a/locales/nl.json +++ b/locales/nl.json @@ -80,6 +80,13 @@ "Manage subscriptions": "Abonnees beheren", "Watch history": "Kijkgeschiedenis", "Delete account": "Account verwijderen", + "Administrator preferences": "", + "Default homepage: ": "", + "Feed menu: ": "", + "Top enabled? ": "", + "CAPTCHA enabled? ": "", + "Login enabled? ": "", + "Registration enabled? ": "", "Save preferences": "Opslaan voorkeuren", "Subscription manager": "Abonnees beheerder", "`x` subscriptions": "`x` abonnees", diff --git a/locales/pl.json b/locales/pl.json index f36765ce..29b6edba 100644 --- a/locales/pl.json +++ b/locales/pl.json @@ -1,280 +1,287 @@ { - "`x` subscribers": "`x` subskrybcji", - "`x` videos": "`x` filmów", - "LIVE": "NA ŻYWO", - "Shared `x` ago": "Udostępniono `x` temu", - "Unsubscribe": "Odsubskrybuj", - "Subscribe": "Subskrybuj", - "Login to subscribe to `x`": "Zaloguj się, aby subskrybować `x`", - "View channel on YouTube": "Wyświetl kanał na YouTube", - "newest": "najnowsze", - "oldest": "najstarsze", - "popular": "popularne", - "Preview page": "Podgląd strony", - "Next page": "Następna strona", - "Clear watch history?": "Wyczyścić historię?", - "Yes": "Tak", - "No": "Nie", - "Import and Export Data": "Import i eksport danych", - "Import": "Import", - "Import Invidious data": "Importuj dane Invidious", - "Import YouTube subscriptions": "Importuj subskrybcje z YouTube", - "Import FreeTube subscriptions (.db)": "Importuj subskrybcje z FreeTube (.db)", - "Import NewPipe subscriptions (.json)": "Importuj subskrybcje z NewPipe (.json)", - "Import NewPipe data (.zip)": "Importuj dane NewPipe (.zip)", - "Export": "Eksport", - "Export subscriptions as OPML": "Eksportuj subskrybcje jako OPML", - "Export subscriptions as OPML (for NewPipe & FreeTube)": "Eksportuj subskrybcje jako OPML (dla NewPipe i FreeTube)", - "Export data as JSON": "Eksportuj dane jako JSON", - "Delete account?": "Usunąć konto?", - "History": "Historia", - "Previous page": "Poprzednia strona", - "An alternative front-end to YouTube": "Alternatywny front-end dla YouTube", - "JavaScript license information": "Informacja o licencji JavaScript", - "source": "źródło", - "Login": "Zaloguj", - "Login/Register": "Zaloguj/Zarejestruj", - "Login to Google": "Zaloguj do Google", - "User ID:": "ID użytkownika:", - "Password:": "Hasło:", - "Time (h:mm:ss):": "Godzina (h:mm:ss):", - "Text CAPTCHA": "Tekst CAPTCHA", - "Image CAPTCHA": "Obraz CAPTCHA", - "Sign In": "Zaloguj się", - "Register": "Zarejestruj się", - "Email:": "Email:", - "Google verification code:": "Kod weryfikacyjny Google:", - "Preferences": "Preferencje", - "Player preferences": "Ustawienia odtwarzacza", - "Always loop: ": "Zawsze zapętlaj: ", - "Autoplay: ": "Autoodtwarzanie: ", - "Autoplay next video: ": "Odtwórz następny film: ", - "Listen by default: ": "Tryb dźwiękowy: ", - "Default speed: ": "Domyślna prędkość: ", - "Preferred video quality: ": "Preferowana jakość filmów: ", - "Player volume: ": "Głośność odtwarzacza: ", - "Default comments: ": "Domyślne komentarze: ", - "Default captions: ": "Domyślne napisy: ", - "Fallback captions: ": "Rezerwowe napisy: ", - "Show related videos? ": "Pokaż powiązane filmy? ", - "Visual preferences": "Preferencje Wizualne", - "Dark mode: ": "Ciemny motyw: ", - "Thin mode: ": "Tryb minimalny: ", - "Subscription preferences": "Preferencje subskrybcji", - "Redirect homepage to feed: ": "Przekieruj stronę główną do subskrybcji: ", - "Number of videos shown in feed: ": "Liczba filmów widoczna na stronie subskrybcji: ", - "Sort videos by: ": "Sortuj filmy po: ", - "published": "czasie publikacji", - "published - reverse": "czasie publikacji od najstarszych", - "alphabetically": "alfabetycznie", - "alphabetically - reverse": "alfabetycznie od tyłu", - "channel name": "nazwie kanału", - "channel name - reverse": "nazwie kanału od tyłu", - "Only show latest video from channel: ": "Pokazuj tylko najnowszy film z kanału: ", - "Only show latest unwatched video from channel: ": "Pokazuj tylko najnowszy nie obejrzany film z kanału: ", - "Only show unwatched: ": "Pokazuj tylko nie obejrzane: ", - "Only show notifications (if there are any): ": "Pokazuj tylko powiadomienia (jeśli są): ", - "Data preferences": "Preferencje danych", - "Clear watch history": "Wyczyść historię", - "Import/Export data": "Import/Eksport danych", - "Manage subscriptions": "Organizuj subskrybcje", - "Watch history": "Historia", - "Delete account": "Usuń konto", - "Save preferences": "Zapisz preferencje", - "Subscription manager": "Manager subskrybcji", - "`x` subscriptions": "`x` subskrybcji", - "Import/Export": "Import/Eksport", - "unsubscribe": "odsubskrybuj", - "Subscriptions": "Subskrybcje", - "`x` unseen notifications": "`x` niewidzianych powiadomień", - "search": "szukaj", - "Sign out": "Wyloguj", - "Released under the AGPLv3 by Omar Roth.": "Wydano na licencji AGPLv3 przez Omar Roth.", - "Source available here.": "Kod źródłowy dostępny tutaj.", - "View JavaScript license information.": "Wyświetl informację o licencji JavaScript.", - "Trending": "Na czasie", - "Watch video on Youtube": "Zobacz film na YouTube", - "Genre: ": "Gatunek: ", - "License: ": "Licencja: ", - "Family friendly? ": "Przyjazny rodzinie? ", - "Wilson score: ": "Punktacja Wilsona: ", - "Engagement: ": "Zaangażowanie: ", - "Whitelisted regions: ": "Dostępny na obszarach: ", - "Blacklisted regions: ": "Niedostępny na obszarach: ", - "Shared `x`": "Udostępniono `x`", - "Hi! Looks like you have JavaScript disabled. Click here to view comments, keep in mind it may take a bit longer to load.": "Cześć! Wygląda na to, że masz wyłączoną obsługę JavaScriptu. Kliknij tutaj, żeby zobaczyć komentarze. Pamiętaj, że wczytywanie może potrwać dłużej.", - "View YouTube comments": "Wyświetl komentarze z YouTube", - "View more comments on Reddit": "Wyświetl więcej komentarzy na Reddicie", - "View `x` comments": "Wyświetl `x` komentarzy", - "View Reddit comments": "Wyświetl komentarze z Redditta", - "Hide replies": "Ukryj odpowiedzi", - "Show replies": "Pokaż odpowiedzi", - "Incorrect password": "Niepoprawne hasło", - "Quota exceeded, try again in a few hours": "Przekroczony limit zapytań, spróbuj ponownie za kilka godzin", - "Unable to login, make sure two-factor authentication (Authenticator or SMS) is enabled.": "Nie udało się zalogować, upewnij się, że dwuetapowe uwierzytelnianie (Autentykator lub SMS) jest aktywne.", - "Invalid TFA code": "Niepoprawny kod TFA", - "Login failed. This may be because two-factor authentication is not enabled on your account.": "Nie udało się zalogować. To może być spowodowane wyłączoną dwustopniową autoryzacją na twoim koncie.", - "Invalid answer": "Niepoprawna odpowiedź", - "Invalid CAPTCHA": "CAPTCHA wykonane błędnie", - "CAPTCHA is a required field": "CAPTCHA jest polem wymaganym", - "User ID is a required field": "ID użytkownika jest polem wymaganym", - "Password is a required field": "Hasło jest polem wymaganym", - "Invalid username or password": "Niepoprawny login lub hasło", - "Please sign in using 'Sign in with Google'": "Zaloguj się używając \"Zaloguj się przez Google\"", - "Password cannot be empty": "Hasło nie może być puste", - "Password cannot be longer than 55 characters": "Hasło nie może być dłuższe niż 55 znaków", - "Please sign in": "Proszę się zalogować", - "Invidious Private Feed for `x`": "", - "channel:`x`": "kanał:`x", - "Deleted or invalid channel": "Usunięty lub niepoprawny kanał", - "This channel does not exist.": "Ten kanał nie istnieje.", - "Could not get channel info.": "Nie udało się uzyskać informacji o kanale.", - "Could not fetch comments": "Nie udało się pobrać komentarzy", - "View `x` replies": "Wyświetl `x` odpowiedzi", - "`x` ago": "`x` temu", - "Load more": "Wczytaj więcej", - "`x` points": "`x` punktów", - "Could not create mix.": "Nie udało się utworzyć miksu.", - "Playlist is empty": "Lista odtwarzania jest pusta", - "Invalid playlist.": "Niepoprawna lista.", - "Playlist does not exist.": "Lista odtwarzania nie istnieje.", - "Could not pull trending pages.": "Nie udało się pobrać strony na czasie.", - "Hidden field \"challenge\" is a required field": "Ukryte pole \"wyzwanie\" jest polem wymaganym", - "Hidden field \"token\" is a required field": "Ukryte pole \"token\" jest polem wymaganym", - "Invalid challenge": "Niepoprawne wyzwanie", - "Invalid token": "Niepoprawny token", - "Invalid user": "Niepoprawny użytkownik", - "Token is expired, please try again": "Token wygasł, spróbuj ponownie", - "English": "angielski", - "English (auto-generated)": "angielski (automatycznie generowane)", - "Afrikaans": "", - "Albanian": "albański", - "Amharic": "", - "Arabic": "arabski", - "Armenian": "", - "Azerbaijani": "", - "Bangla": "", - "Basque": "", - "Belarusian": "białoruski", - "Bosnian": "bośniacki", - "Bulgarian": "bułgarski", - "Burmese": "birmański", - "Catalan": "kataloński", - "Cebuano": "", - "Chinese (Simplified)": "chiński (uproszczony)", - "Chinese (Traditional)": "chiński (tradycyjny)", - "Corsican": "korsykański", - "Croatian": "chorwacki", - "Czech": "czeski", - "Danish": "duński", - "Dutch": "holenderski", - "Esperanto": "esperanto", - "Estonian": "estoński", - "Filipino": "filipiński", - "Finnish": "fiński", - "French": "francuski", - "Galician": "galicyjski", - "Georgian": "gruziński", - "German": "niemiecki", - "Greek": "grecki", - "Gujarati": "", - "Haitian Creole": "", - "Hausa": "", - "Hawaiian": "hawajski", - "Hebrew": "hebrajski", - "Hindi": "hindi", - "Hmong": "", - "Hungarian": "węgierski", - "Icelandic": "islandzki", - "Igbo": "", - "Indonesian": "indonezyjski", - "Irish": "irlandzki", - "Italian": "włoski", - "Japanese": "japoński", - "Javanese": "jawajski", - "Kannada": "", - "Kazakh": "kazachski", - "Khmer": "", - "Korean": "koreański", - "Kurdish": "kurdyjski", - "Kyrgyz": "kirgiski", - "Lao": "", - "Latin": "łaciński", - "Latvian": "łotewski", - "Lithuanian": "litewski", - "Luxembourgish": "luksemburski", - "Macedonian": "macedoński", - "Malagasy": "malgaski", - "Malay": "malajski", - "Malayalam": "", - "Maltese": "maltański", - "Maori": "", - "Marathi": "", - "Mongolian": "mongolski", - "Nepali": "nepalski", - "Norwegian": "norweski", - "Nyanja": "", - "Pashto": "", - "Persian": "perski", - "Polish": "polski", - "Portuguese": "portugalski", - "Punjabi": "", - "Romanian": "rumuński", - "Russian": "rosyjski", - "Samoan": "", - "Scottish Gaelic": "", - "Serbian": "serbski", - "Shona": "", - "Sindhi": "", - "Sinhala": "", - "Slovak": "słowacki", - "Slovenian": "słoweński", - "Somali": "somalijski", - "Southern Sotho": "", - "Spanish": "hiszpański", - "Spanish (Latin America)": "hiszpański (ameryka łacińska)", - "Sundanese": "", - "Swahili": "", - "Swedish": "szwedzki", - "Tajik": "", - "Tamil": "", - "Telugu": "", - "Thai": "tajski", - "Turkish": "turecki", - "Ukrainian": "ukraiński", - "Urdu": "", - "Uzbek": "uzbecki", - "Vietnamese": "wietnamski", - "Welsh": "walijski", - "Western Frisian": "", - "Xhosa": "", - "Yiddish": "", - "Yoruba": "", - "Zulu": "", - "`x` years": "`x` lat", - "`x` months": "`x` miesięcy", - "`x` weeks": "`x` tygodni", - "`x` days": "`x` dni", - "`x` hours": "`x` godzin", - "`x` minutes": "`x` minut", - "`x` seconds": "`x` sekund", - "Fallback comments: ": "Zastępcze komentarze: ", - "Popular": "Popularne", - "Top": "Na czasie", - "About": "Informacje", - "Rating: ": "Ocena: ", - "Language: ": "Język: ", - "Default": "", - "Music": "Muzyka", - "Gaming": "Gry", - "News": "Wiadomości", - "Movies": "Filmy", - "Download": "Pobierz", - "Download as: ": "Pobierz jako: ", - "%A %B %-d, %Y": "", - "(edited)": "(edytowany)", - "Youtube permalink of the comment": "Odnośnik bezpośredni do komentarza na YouTube", - "`x` marked it with a ❤": "", - "Audio mode": "Tryb audio", - "Video mode": "Tryb wideo" + "`x` subscribers": "`x` subskrybcji", + "`x` videos": "`x` filmów", + "LIVE": "NA ŻYWO", + "Shared `x` ago": "Udostępniono `x` temu", + "Unsubscribe": "Odsubskrybuj", + "Subscribe": "Subskrybuj", + "Login to subscribe to `x`": "Zaloguj się, aby subskrybować `x`", + "View channel on YouTube": "Wyświetl kanał na YouTube", + "newest": "najnowsze", + "oldest": "najstarsze", + "popular": "popularne", + "Preview page": "Podgląd strony", + "Next page": "Następna strona", + "Clear watch history?": "Wyczyścić historię?", + "Yes": "Tak", + "No": "Nie", + "Import and Export Data": "Import i eksport danych", + "Import": "Import", + "Import Invidious data": "Importuj dane Invidious", + "Import YouTube subscriptions": "Importuj subskrybcje z YouTube", + "Import FreeTube subscriptions (.db)": "Importuj subskrybcje z FreeTube (.db)", + "Import NewPipe subscriptions (.json)": "Importuj subskrybcje z NewPipe (.json)", + "Import NewPipe data (.zip)": "Importuj dane NewPipe (.zip)", + "Export": "Eksport", + "Export subscriptions as OPML": "Eksportuj subskrybcje jako OPML", + "Export subscriptions as OPML (for NewPipe & FreeTube)": "Eksportuj subskrybcje jako OPML (dla NewPipe i FreeTube)", + "Export data as JSON": "Eksportuj dane jako JSON", + "Delete account?": "Usunąć konto?", + "History": "Historia", + "Previous page": "Poprzednia strona", + "An alternative front-end to YouTube": "Alternatywny front-end dla YouTube", + "JavaScript license information": "Informacja o licencji JavaScript", + "source": "źródło", + "Login": "Zaloguj", + "Login/Register": "Zaloguj/Zarejestruj", + "Login to Google": "Zaloguj do Google", + "User ID:": "ID użytkownika:", + "Password:": "Hasło:", + "Time (h:mm:ss):": "Godzina (h:mm:ss):", + "Text CAPTCHA": "Tekst CAPTCHA", + "Image CAPTCHA": "Obraz CAPTCHA", + "Sign In": "Zaloguj się", + "Register": "Zarejestruj się", + "Email:": "Email:", + "Google verification code:": "Kod weryfikacyjny Google:", + "Preferences": "Preferencje", + "Player preferences": "Ustawienia odtwarzacza", + "Always loop: ": "Zawsze zapętlaj: ", + "Autoplay: ": "Autoodtwarzanie: ", + "Autoplay next video: ": "Odtwórz następny film: ", + "Listen by default: ": "Tryb dźwiękowy: ", + "Default speed: ": "Domyślna prędkość: ", + "Preferred video quality: ": "Preferowana jakość filmów: ", + "Player volume: ": "Głośność odtwarzacza: ", + "Default comments: ": "Domyślne komentarze: ", + "Default captions: ": "Domyślne napisy: ", + "Fallback captions: ": "Rezerwowe napisy: ", + "Show related videos? ": "Pokaż powiązane filmy? ", + "Visual preferences": "Preferencje Wizualne", + "Dark mode: ": "Ciemny motyw: ", + "Thin mode: ": "Tryb minimalny: ", + "Subscription preferences": "Preferencje subskrybcji", + "Redirect homepage to feed: ": "Przekieruj stronę główną do subskrybcji: ", + "Number of videos shown in feed: ": "Liczba filmów widoczna na stronie subskrybcji: ", + "Sort videos by: ": "Sortuj filmy po: ", + "published": "czasie publikacji", + "published - reverse": "czasie publikacji od najstarszych", + "alphabetically": "alfabetycznie", + "alphabetically - reverse": "alfabetycznie od tyłu", + "channel name": "nazwie kanału", + "channel name - reverse": "nazwie kanału od tyłu", + "Only show latest video from channel: ": "Pokazuj tylko najnowszy film z kanału: ", + "Only show latest unwatched video from channel: ": "Pokazuj tylko najnowszy nie obejrzany film z kanału: ", + "Only show unwatched: ": "Pokazuj tylko nie obejrzane: ", + "Only show notifications (if there are any): ": "Pokazuj tylko powiadomienia (jeśli są): ", + "Data preferences": "Preferencje danych", + "Clear watch history": "Wyczyść historię", + "Import/Export data": "Import/Eksport danych", + "Manage subscriptions": "Organizuj subskrybcje", + "Watch history": "Historia", + "Delete account": "Usuń konto", + "Administrator preferences": "", + "Default homepage: ": "", + "Feed menu: ": "", + "Top enabled? ": "", + "CAPTCHA enabled? ": "", + "Login enabled? ": "", + "Registration enabled? ": "", + "Save preferences": "Zapisz preferencje", + "Subscription manager": "Manager subskrybcji", + "`x` subscriptions": "`x` subskrybcji", + "Import/Export": "Import/Eksport", + "unsubscribe": "odsubskrybuj", + "Subscriptions": "Subskrybcje", + "`x` unseen notifications": "`x` niewidzianych powiadomień", + "search": "szukaj", + "Sign out": "Wyloguj", + "Released under the AGPLv3 by Omar Roth.": "Wydano na licencji AGPLv3 przez Omar Roth.", + "Source available here.": "Kod źródłowy dostępny tutaj.", + "View JavaScript license information.": "Wyświetl informację o licencji JavaScript.", + "Trending": "Na czasie", + "Watch video on Youtube": "Zobacz film na YouTube", + "Genre: ": "Gatunek: ", + "License: ": "Licencja: ", + "Family friendly? ": "Przyjazny rodzinie? ", + "Wilson score: ": "Punktacja Wilsona: ", + "Engagement: ": "Zaangażowanie: ", + "Whitelisted regions: ": "Dostępny na obszarach: ", + "Blacklisted regions: ": "Niedostępny na obszarach: ", + "Shared `x`": "Udostępniono `x`", + "Hi! Looks like you have JavaScript disabled. Click here to view comments, keep in mind it may take a bit longer to load.": "Cześć! Wygląda na to, że masz wyłączoną obsługę JavaScriptu. Kliknij tutaj, żeby zobaczyć komentarze. Pamiętaj, że wczytywanie może potrwać dłużej.", + "View YouTube comments": "Wyświetl komentarze z YouTube", + "View more comments on Reddit": "Wyświetl więcej komentarzy na Reddicie", + "View `x` comments": "Wyświetl `x` komentarzy", + "View Reddit comments": "Wyświetl komentarze z Redditta", + "Hide replies": "Ukryj odpowiedzi", + "Show replies": "Pokaż odpowiedzi", + "Incorrect password": "Niepoprawne hasło", + "Quota exceeded, try again in a few hours": "Przekroczony limit zapytań, spróbuj ponownie za kilka godzin", + "Unable to login, make sure two-factor authentication (Authenticator or SMS) is enabled.": "Nie udało się zalogować, upewnij się, że dwuetapowe uwierzytelnianie (Autentykator lub SMS) jest aktywne.", + "Invalid TFA code": "Niepoprawny kod TFA", + "Login failed. This may be because two-factor authentication is not enabled on your account.": "Nie udało się zalogować. To może być spowodowane wyłączoną dwustopniową autoryzacją na twoim koncie.", + "Invalid answer": "Niepoprawna odpowiedź", + "Invalid CAPTCHA": "CAPTCHA wykonane błędnie", + "CAPTCHA is a required field": "CAPTCHA jest polem wymaganym", + "User ID is a required field": "ID użytkownika jest polem wymaganym", + "Password is a required field": "Hasło jest polem wymaganym", + "Invalid username or password": "Niepoprawny login lub hasło", + "Please sign in using 'Sign in with Google'": "Zaloguj się używając \"Zaloguj się przez Google\"", + "Password cannot be empty": "Hasło nie może być puste", + "Password cannot be longer than 55 characters": "Hasło nie może być dłuższe niż 55 znaków", + "Please sign in": "Proszę się zalogować", + "Invidious Private Feed for `x`": "", + "channel:`x`": "kanał:`x", + "Deleted or invalid channel": "Usunięty lub niepoprawny kanał", + "This channel does not exist.": "Ten kanał nie istnieje.", + "Could not get channel info.": "Nie udało się uzyskać informacji o kanale.", + "Could not fetch comments": "Nie udało się pobrać komentarzy", + "View `x` replies": "Wyświetl `x` odpowiedzi", + "`x` ago": "`x` temu", + "Load more": "Wczytaj więcej", + "`x` points": "`x` punktów", + "Could not create mix.": "Nie udało się utworzyć miksu.", + "Playlist is empty": "Lista odtwarzania jest pusta", + "Invalid playlist.": "Niepoprawna lista.", + "Playlist does not exist.": "Lista odtwarzania nie istnieje.", + "Could not pull trending pages.": "Nie udało się pobrać strony na czasie.", + "Hidden field \"challenge\" is a required field": "Ukryte pole \"wyzwanie\" jest polem wymaganym", + "Hidden field \"token\" is a required field": "Ukryte pole \"token\" jest polem wymaganym", + "Invalid challenge": "Niepoprawne wyzwanie", + "Invalid token": "Niepoprawny token", + "Invalid user": "Niepoprawny użytkownik", + "Token is expired, please try again": "Token wygasł, spróbuj ponownie", + "English": "angielski", + "English (auto-generated)": "angielski (automatycznie generowane)", + "Afrikaans": "", + "Albanian": "albański", + "Amharic": "", + "Arabic": "arabski", + "Armenian": "", + "Azerbaijani": "", + "Bangla": "", + "Basque": "", + "Belarusian": "białoruski", + "Bosnian": "bośniacki", + "Bulgarian": "bułgarski", + "Burmese": "birmański", + "Catalan": "kataloński", + "Cebuano": "", + "Chinese (Simplified)": "chiński (uproszczony)", + "Chinese (Traditional)": "chiński (tradycyjny)", + "Corsican": "korsykański", + "Croatian": "chorwacki", + "Czech": "czeski", + "Danish": "duński", + "Dutch": "holenderski", + "Esperanto": "esperanto", + "Estonian": "estoński", + "Filipino": "filipiński", + "Finnish": "fiński", + "French": "francuski", + "Galician": "galicyjski", + "Georgian": "gruziński", + "German": "niemiecki", + "Greek": "grecki", + "Gujarati": "", + "Haitian Creole": "", + "Hausa": "", + "Hawaiian": "hawajski", + "Hebrew": "hebrajski", + "Hindi": "hindi", + "Hmong": "", + "Hungarian": "węgierski", + "Icelandic": "islandzki", + "Igbo": "", + "Indonesian": "indonezyjski", + "Irish": "irlandzki", + "Italian": "włoski", + "Japanese": "japoński", + "Javanese": "jawajski", + "Kannada": "", + "Kazakh": "kazachski", + "Khmer": "", + "Korean": "koreański", + "Kurdish": "kurdyjski", + "Kyrgyz": "kirgiski", + "Lao": "", + "Latin": "łaciński", + "Latvian": "łotewski", + "Lithuanian": "litewski", + "Luxembourgish": "luksemburski", + "Macedonian": "macedoński", + "Malagasy": "malgaski", + "Malay": "malajski", + "Malayalam": "", + "Maltese": "maltański", + "Maori": "", + "Marathi": "", + "Mongolian": "mongolski", + "Nepali": "nepalski", + "Norwegian": "norweski", + "Nyanja": "", + "Pashto": "", + "Persian": "perski", + "Polish": "polski", + "Portuguese": "portugalski", + "Punjabi": "", + "Romanian": "rumuński", + "Russian": "rosyjski", + "Samoan": "", + "Scottish Gaelic": "", + "Serbian": "serbski", + "Shona": "", + "Sindhi": "", + "Sinhala": "", + "Slovak": "słowacki", + "Slovenian": "słoweński", + "Somali": "somalijski", + "Southern Sotho": "", + "Spanish": "hiszpański", + "Spanish (Latin America)": "hiszpański (ameryka łacińska)", + "Sundanese": "", + "Swahili": "", + "Swedish": "szwedzki", + "Tajik": "", + "Tamil": "", + "Telugu": "", + "Thai": "tajski", + "Turkish": "turecki", + "Ukrainian": "ukraiński", + "Urdu": "", + "Uzbek": "uzbecki", + "Vietnamese": "wietnamski", + "Welsh": "walijski", + "Western Frisian": "", + "Xhosa": "", + "Yiddish": "", + "Yoruba": "", + "Zulu": "", + "`x` years": "`x` lat", + "`x` months": "`x` miesięcy", + "`x` weeks": "`x` tygodni", + "`x` days": "`x` dni", + "`x` hours": "`x` godzin", + "`x` minutes": "`x` minut", + "`x` seconds": "`x` sekund", + "Fallback comments: ": "Zastępcze komentarze: ", + "Popular": "Popularne", + "Top": "Na czasie", + "About": "Informacje", + "Rating: ": "Ocena: ", + "Language: ": "Język: ", + "Default": "", + "Music": "Muzyka", + "Gaming": "Gry", + "News": "Wiadomości", + "Movies": "Filmy", + "Download": "Pobierz", + "Download as: ": "Pobierz jako: ", + "%A %B %-d, %Y": "", + "(edited)": "(edytowany)", + "Youtube permalink of the comment": "Odnośnik bezpośredni do komentarza na YouTube", + "`x` marked it with a ❤": "", + "Audio mode": "Tryb audio", + "Video mode": "Tryb wideo" } diff --git a/locales/ru.json b/locales/ru.json index 64f6ceca..9a5f0c57 100644 --- a/locales/ru.json +++ b/locales/ru.json @@ -1,286 +1,293 @@ { - "`x` subscribers": "`x` подписчиков", - "`x` videos": "`x` видео", - "LIVE": "ПРЯМОЙ ЭФИР", - "Shared `x` ago": "Опубликовано `x` назад", - "Unsubscribe": "Отписаться", - "Subscribe": "Подписаться", - "Login to subscribe to `x`": "Войти, чтобы подписаться на `x`", - "View channel on YouTube": "Канал на YouTube", - "newest": "новые", - "oldest": "старые", - "popular": "популярные", - "Preview page": "Предварительный просмотр", - "Next page": "Следующая страница", - "Clear watch history?": "Очистить историю просмотров?", - "Yes": "Да", - "No": "Нет", - "Import and Export Data": "Импорт и экспорт данных", - "Import": "Импорт", - "Import Invidious data": "Импортировать данные Invidious", - "Import YouTube subscriptions": "Импортировать YouTube подписки", - "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", - "Delete account?": "Удалить аккаунт?", - "History": "История", - "Previous page": "Предыдущая страница", - "An alternative front-end to YouTube": "Альтернативный фронтенд для YouTube", - "JavaScript license information": "Лицензии JavaScript", - "source": "источник", - "Login": "Войти", - "Login/Register": "Войти/Регистрация", - "Login to Google": "Войти через Google", - "User ID:": "ID пользователя:", - "Password:": "Пароль:", - "Time (h:mm:ss):": "Время (ч:мм:сс):", - "Text CAPTCHA": "Текст капчи", - "Image CAPTCHA": "Изображение капчи", - "Sign In": "Войти", - "Register": "Регистрация", - "Email:": "Эл. почта:", - "Google verification code:": "Код подтверждения Google:", - "Preferences": "Настройки", - "Player preferences": "Настройки проигрывателя", - "Always loop: ": "Всегда повторять: ", - "Autoplay: ": "Автовоспроизведение: ", - "Autoplay next video: ": "Автовоспроизведение следующего видео: ", - "Listen by default: ": "Режим \"только аудио\" по-умолчанию: ", - "Default speed: ": "Скорость по-умолчанию: ", - "Preferred video quality: ": "Предпочтительное качество видео: ", - "Player volume: ": "Громкость воспроизведения: ", - "Default comments: ": "Источник комментариев: ", - "youtube": "YouTube", - "reddit": "Reddit", - "Default captions: ": "Субтитры по-умолчанию: ", - "Fallback captions: ": "Резервные субтитры: ", - "Show related videos? ": "Показывать похожие видео? ", - "Visual preferences": "Визуальные настройки", - "Dark mode: ": "Темная тема: ", - "Thin mode: ": "Облегченный режим: ", - "Subscription preferences": "Настройки подписок", - "Redirect homepage to feed: ": "Отображать ленту вместо главной страницы: ", - "Number of videos shown in feed: ": "Число видео в ленте: ", - "Sort videos by: ": "Сортировать видео по: ", - "published": "дате публикации", - "published - reverse": "дате - обратный порядок", - "alphabetically": "алфавиту", - "alphabetically - reverse": "алфавиту - обратный порядок", - "channel name": "имени канала", - "channel name - reverse": "имени канала - обратный порядок", - "Only show latest video from channel: ": "Отображать только последние видео с каждого канала: ", - "Only show latest unwatched video from channel: ": "Отображать только непросмотренные видео с каждого канала: ", - "Only show unwatched: ": "Отображать только непросмотренные видео: ", - "Only show notifications (if there are any): ": "Отображать только оповещения (если есть): ", - "Data preferences": "Настройки данных", - "Clear watch history": "Очистить историю просмотра", - "Import/Export data": "Импорт/Экспорт данных", - "Manage subscriptions": "Управление подписками", - "Watch history": "История просмотров", - "Delete account": "Удалить аккаунт", - "Save preferences": "Сохранить настройки", - "Subscription manager": "Менеджер подписок", - "`x` subscriptions": "`x` подписок", - "Import/Export": "Импорт/Экспорт", - "unsubscribe": "отписаться", - "Subscriptions": "Подписки", - "`x` unseen notifications": "`x` новых оповещений", - "search": "поиск", - "Sign out": "Выйти", - "Released under the AGPLv3 by Omar Roth.": "Распространяется Omar Roth по AGPLv3.", - "Source available here.": "Исходный код доступен здесь.", - "Liberapay: ": "Liberapay: ", - "Patreon: ": "Patreon: ", - "BTC: ": "BTC: ", - "BCH: ": "BCH: ", - "View JavaScript license information.": "Посмотреть лицензии JavaScript кода.", - "Trending": "В тренде", - "Watch video on Youtube": "Смотреть на YouTube", - "Genre: ": "Жанр: ", - "License: ": "Лицензия: ", - "Family friendly? ": "Семейный просмотр: ", - "Wilson score: ": "Рейтинг Вильсона: ", - "Engagement: ": "Вовлеченность: ", - "Whitelisted regions: ": "Доступно для: ", - "Blacklisted regions: ": "Недоступно для: ", - "Shared `x`": "Опубликовано `x`", - "Hi! Looks like you have JavaScript disabled. Click here to view comments, keep in mind it may take a bit longer to load.": "Похоже, что у Вас отключен JavaScript. Нажмите сюда, чтобы увидеть комментарии (учтите, что они могут загружаться дольше).", - "View YouTube comments": "Смотреть комментарии с YouTube", - "View more comments on Reddit": "Больше комментариев на Reddit", - "View `x` comments": "Показать `x` комментариев", - "View Reddit comments": "Смотреть комментарии с Reddit", - "Hide replies": "Скрыть ответы", - "Show replies": "Показать ответы", - "Incorrect password": "Неправильный пароль", - "Quota exceeded, try again in a few hours": "Превышена квота, попробуйте снова через несколько часов", - "Unable to login, make sure two-factor authentication (Authenticator or SMS) is enabled.": "Вход не выполнен, проверьте, не включена ли двухфакторная аутентификация.", - "Invalid TFA code": "Неправильный TFA код", - "Login failed. This may be because two-factor authentication is not enabled on your account.": "Не удалось войти. Это может быть из-за того, что в вашем аккаунте не включена двухфакторная аутентификация.", - "Invalid answer": "Неверный ответ", - "Invalid CAPTCHA": "Неверная капча", - "CAPTCHA is a required field": "Необходимо ввести капчу", - "User ID is a required field": "Необходимо ввести идентификатор пользователя", - "Password is a required field": "Необходимо ввести пароль", - "Invalid username or password": "Недопустимый пароль или имя пользователя", - "Please sign in using 'Sign in with Google'": "Пожалуйста войдите через Google", - "Password cannot be empty": "Пароль не может быть пустым", - "Password cannot be longer than 55 characters": "Пароль не может быть длиннее 55 символов", - "Please sign in": "Пожалуйста, войдите", - "Invidious Private Feed for `x`": "Приватная лента Invidious для `x`", - "channel:`x`": "канал: `x`", - "Deleted or invalid channel": "Канал удален или не найден", - "This channel does not exist.": "Такой канал не существует.", - "Could not get channel info.": "Невозможно получить информацию о канале.", - "Could not fetch comments": "Невозможно получить комментарии", - "View `x` replies": "Показать `x` ответов", - "`x` ago": "`x` назад", - "Load more": "Загрузить больше", - "`x` points": "`x` очков", - "Could not create mix.": "Невозможно создать \"микс\".", - "Playlist is empty": "Плейлист пуст", - "Invalid playlist.": "Некорректный плейлист.", - "Playlist does not exist.": "Плейлист не существует.", - "Could not pull trending pages.": "Невозможно получить страницы \"в тренде\".", - "Hidden field \"challenge\" is a required field": "Необходимо заполнить скрытое поле \"challenge\"", - "Hidden field \"token\" is a required field": "Необходимо заполнить скрытое поле \"токен\"", - "Invalid challenge": "Неправильный ответ в \"challenge\"", - "Invalid token": "Неправильный токен", - "Invalid user": "Недопустимое имя пользователя", - "Token is expired, please try again": "Срок действия токена истек, попробуйте позже", - "English": "Английский", - "English (auto-generated)": "Английский (созданы автоматически)", - "Afrikaans": "Африкаанс", - "Albanian": "Албанский", - "Amharic": "Амхарский", - "Arabic": "Арабский", - "Armenian": "Армянский", - "Azerbaijani": "Азербайджанский", - "Bangla": "Бенгальский", - "Basque": "Баскский", - "Belarusian": "Белорусский", - "Bosnian": "Боснийский", - "Bulgarian": "Болгарский", - "Burmese": "Бирманский", - "Catalan": "Каталонский", - "Cebuano": "Себуанский", - "Chinese (Simplified)": "Китайский (упрощенный)", - "Chinese (Traditional)": "Китайский (традиционный)", - "Corsican": "Корсиканский", - "Croatian": "Хорватский", - "Czech": "Чешский", - "Danish": "Датский", - "Dutch": "Нидерландский", - "Esperanto": "Эсперанто", - "Estonian": "Эстонский", - "Filipino": "Филиппинский", - "Finnish": "Финский", - "French": "Французский", - "Galician": "Галисийский", - "Georgian": "Грузинский", - "German": "Немецкий", - "Greek": "Греческий", - "Gujarati": "Гуджаратский", - "Haitian Creole": "Гаит. креольский", - "Hausa": "Хауса", - "Hawaiian": "Гавайский", - "Hebrew": "Иврит", - "Hindi": "Хинди", - "Hmong": "Хмонг (мяо)", - "Hungarian": "Венгерский", - "Icelandic": "Исландский", - "Igbo": "Игбо", - "Indonesian": "Индонезийский", - "Irish": "Ирландский", - "Italian": "Итальянский", - "Japanese": "Японский", - "Javanese": "Яванский", - "Kannada": "Каннада", - "Kazakh": "Казахский", - "Khmer": "Кхмерский", - "Korean": "Корейский", - "Kurdish": "Курдский", - "Kyrgyz": "Киргизский", - "Lao": "Лаосский", - "Latin": "Латинский", - "Latvian": "Латышский", - "Lithuanian": "Литовский", - "Luxembourgish": "Люксембургский", - "Macedonian": "Македонский", - "Malagasy": "Малагасийский", - "Malay": "Малайский", - "Malayalam": "Малаялам", - "Maltese": "Мальтийский", - "Maori": "Маори", - "Marathi": "Маратхи", - "Mongolian": "Монгольская", - "Nepali": "Непальский", - "Norwegian": "Норвежский", - "Nyanja": "Ньянджа", - "Pashto": "Пушту", - "Persian": "Персидский", - "Polish": "Польский", - "Portuguese": "Португальский", - "Punjabi": "Панджаби", - "Romanian": "Румынский", - "Russian": "Русский", - "Samoan": "Самоанский", - "Scottish Gaelic": "Шотландский (гэльский)", - "Serbian": "Сербский", - "Shona": "Шона", - "Sindhi": "Синдхи", - "Sinhala": "Сингальский", - "Slovak": "Словацкий", - "Slovenian": "Словенский", - "Somali": "Сомалийский", - "Southern Sotho": "Сесото (южный сото)", - "Spanish": "Испанский", - "Spanish (Latin America)": "Испанский (Латинская Америка)", - "Sundanese": "Сунданский", - "Swahili": "Суахили", - "Swedish": "Шведский", - "Tajik": "Таджикский", - "Tamil": "Тамильский", - "Telugu": "Телугу", - "Thai": "Тайский", - "Turkish": "Турецкий", - "Ukrainian": "Украинский", - "Urdu": "Урду", - "Uzbek": "Узбекский", - "Vietnamese": "Вьетнамский", - "Welsh": "Валлийский", - "Western Frisian": "Западнофризский", - "Xhosa": "Коса", - "Yiddish": "Идиш", - "Yoruba": "Йоруба", - "Zulu": "Зулусский", - "`x` years": "`x` лет", - "`x` months": "`x` месяцев", - "`x` weeks": "`x` недель", - "`x` days": "`x` дней", - "`x` hours": "`x` часов", - "`x` minutes": "`x` минут", - "`x` seconds": "`x` секунд", - "Fallback comments: ": "Резервные комментарии: ", - "Popular": "Популярное", - "Top": "Топ", - "About": "О сайте", - "Rating: ": "Рейтинг: ", - "Language: ": "Язык: ", - "Default": "По-умолчанию", - "Music": "Музыка", - "Gaming": "Игры", - "News": "Новости", - "Movies": "Фильмы", - "Download": "Скачать", - "Download as: ": "Скачать как: ", - "%A %B %-d, %Y": "%-d %B %Y, %A", - "(edited)": "(изменено)", - "Youtube permalink of the comment": "Прямая ссылка на YouTube", - "`x` marked it with a ❤": "❤ от автора канала \"`x`\"", - "Audio mode": "Аудио режим", - "Video mode": "Видео режим" + "`x` subscribers": "`x` подписчиков", + "`x` videos": "`x` видео", + "LIVE": "ПРЯМОЙ ЭФИР", + "Shared `x` ago": "Опубликовано `x` назад", + "Unsubscribe": "Отписаться", + "Subscribe": "Подписаться", + "Login to subscribe to `x`": "Войти, чтобы подписаться на `x`", + "View channel on YouTube": "Канал на YouTube", + "newest": "новые", + "oldest": "старые", + "popular": "популярные", + "Preview page": "Предварительный просмотр", + "Next page": "Следующая страница", + "Clear watch history?": "Очистить историю просмотров?", + "Yes": "Да", + "No": "Нет", + "Import and Export Data": "Импорт и экспорт данных", + "Import": "Импорт", + "Import Invidious data": "Импортировать данные Invidious", + "Import YouTube subscriptions": "Импортировать YouTube подписки", + "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", + "Delete account?": "Удалить аккаунт?", + "History": "История", + "Previous page": "Предыдущая страница", + "An alternative front-end to YouTube": "Альтернативный фронтенд для YouTube", + "JavaScript license information": "Лицензии JavaScript", + "source": "источник", + "Login": "Войти", + "Login/Register": "Войти/Регистрация", + "Login to Google": "Войти через Google", + "User ID:": "ID пользователя:", + "Password:": "Пароль:", + "Time (h:mm:ss):": "Время (ч:мм:сс):", + "Text CAPTCHA": "Текст капчи", + "Image CAPTCHA": "Изображение капчи", + "Sign In": "Войти", + "Register": "Регистрация", + "Email:": "Эл. почта:", + "Google verification code:": "Код подтверждения Google:", + "Preferences": "Настройки", + "Player preferences": "Настройки проигрывателя", + "Always loop: ": "Всегда повторять: ", + "Autoplay: ": "Автовоспроизведение: ", + "Autoplay next video: ": "Автовоспроизведение следующего видео: ", + "Listen by default: ": "Режим \"только аудио\" по-умолчанию: ", + "Default speed: ": "Скорость по-умолчанию: ", + "Preferred video quality: ": "Предпочтительное качество видео: ", + "Player volume: ": "Громкость воспроизведения: ", + "Default comments: ": "Источник комментариев: ", + "youtube": "YouTube", + "reddit": "Reddit", + "Default captions: ": "Субтитры по-умолчанию: ", + "Fallback captions: ": "Резервные субтитры: ", + "Show related videos? ": "Показывать похожие видео? ", + "Visual preferences": "Визуальные настройки", + "Dark mode: ": "Темная тема: ", + "Thin mode: ": "Облегченный режим: ", + "Subscription preferences": "Настройки подписок", + "Redirect homepage to feed: ": "Отображать ленту вместо главной страницы: ", + "Number of videos shown in feed: ": "Число видео в ленте: ", + "Sort videos by: ": "Сортировать видео по: ", + "published": "дате публикации", + "published - reverse": "дате - обратный порядок", + "alphabetically": "алфавиту", + "alphabetically - reverse": "алфавиту - обратный порядок", + "channel name": "имени канала", + "channel name - reverse": "имени канала - обратный порядок", + "Only show latest video from channel: ": "Отображать только последние видео с каждого канала: ", + "Only show latest unwatched video from channel: ": "Отображать только непросмотренные видео с каждого канала: ", + "Only show unwatched: ": "Отображать только непросмотренные видео: ", + "Only show notifications (if there are any): ": "Отображать только оповещения (если есть): ", + "Data preferences": "Настройки данных", + "Clear watch history": "Очистить историю просмотра", + "Import/Export data": "Импорт/Экспорт данных", + "Manage subscriptions": "Управление подписками", + "Watch history": "История просмотров", + "Delete account": "Удалить аккаунт", + "Administrator preferences": "", + "Default homepage: ": "", + "Feed menu: ": "", + "Top enabled? ": "", + "CAPTCHA enabled? ": "", + "Login enabled? ": "", + "Registration enabled? ": "", + "Save preferences": "Сохранить настройки", + "Subscription manager": "Менеджер подписок", + "`x` subscriptions": "`x` подписок", + "Import/Export": "Импорт/Экспорт", + "unsubscribe": "отписаться", + "Subscriptions": "Подписки", + "`x` unseen notifications": "`x` новых оповещений", + "search": "поиск", + "Sign out": "Выйти", + "Released under the AGPLv3 by Omar Roth.": "Распространяется Omar Roth по AGPLv3.", + "Source available here.": "Исходный код доступен здесь.", + "Liberapay: ": "Liberapay: ", + "Patreon: ": "Patreon: ", + "BTC: ": "BTC: ", + "BCH: ": "BCH: ", + "View JavaScript license information.": "Посмотреть лицензии JavaScript кода.", + "Trending": "В тренде", + "Watch video on Youtube": "Смотреть на YouTube", + "Genre: ": "Жанр: ", + "License: ": "Лицензия: ", + "Family friendly? ": "Семейный просмотр: ", + "Wilson score: ": "Рейтинг Вильсона: ", + "Engagement: ": "Вовлеченность: ", + "Whitelisted regions: ": "Доступно для: ", + "Blacklisted regions: ": "Недоступно для: ", + "Shared `x`": "Опубликовано `x`", + "Hi! Looks like you have JavaScript disabled. Click here to view comments, keep in mind it may take a bit longer to load.": "Похоже, что у Вас отключен JavaScript. Нажмите сюда, чтобы увидеть комментарии (учтите, что они могут загружаться дольше).", + "View YouTube comments": "Смотреть комментарии с YouTube", + "View more comments on Reddit": "Больше комментариев на Reddit", + "View `x` comments": "Показать `x` комментариев", + "View Reddit comments": "Смотреть комментарии с Reddit", + "Hide replies": "Скрыть ответы", + "Show replies": "Показать ответы", + "Incorrect password": "Неправильный пароль", + "Quota exceeded, try again in a few hours": "Превышена квота, попробуйте снова через несколько часов", + "Unable to login, make sure two-factor authentication (Authenticator or SMS) is enabled.": "Вход не выполнен, проверьте, не включена ли двухфакторная аутентификация.", + "Invalid TFA code": "Неправильный TFA код", + "Login failed. This may be because two-factor authentication is not enabled on your account.": "Не удалось войти. Это может быть из-за того, что в вашем аккаунте не включена двухфакторная аутентификация.", + "Invalid answer": "Неверный ответ", + "Invalid CAPTCHA": "Неверная капча", + "CAPTCHA is a required field": "Необходимо ввести капчу", + "User ID is a required field": "Необходимо ввести идентификатор пользователя", + "Password is a required field": "Необходимо ввести пароль", + "Invalid username or password": "Недопустимый пароль или имя пользователя", + "Please sign in using 'Sign in with Google'": "Пожалуйста войдите через Google", + "Password cannot be empty": "Пароль не может быть пустым", + "Password cannot be longer than 55 characters": "Пароль не может быть длиннее 55 символов", + "Please sign in": "Пожалуйста, войдите", + "Invidious Private Feed for `x`": "Приватная лента Invidious для `x`", + "channel:`x`": "канал: `x`", + "Deleted or invalid channel": "Канал удален или не найден", + "This channel does not exist.": "Такой канал не существует.", + "Could not get channel info.": "Невозможно получить информацию о канале.", + "Could not fetch comments": "Невозможно получить комментарии", + "View `x` replies": "Показать `x` ответов", + "`x` ago": "`x` назад", + "Load more": "Загрузить больше", + "`x` points": "`x` очков", + "Could not create mix.": "Невозможно создать \"микс\".", + "Playlist is empty": "Плейлист пуст", + "Invalid playlist.": "Некорректный плейлист.", + "Playlist does not exist.": "Плейлист не существует.", + "Could not pull trending pages.": "Невозможно получить страницы \"в тренде\".", + "Hidden field \"challenge\" is a required field": "Необходимо заполнить скрытое поле \"challenge\"", + "Hidden field \"token\" is a required field": "Необходимо заполнить скрытое поле \"токен\"", + "Invalid challenge": "Неправильный ответ в \"challenge\"", + "Invalid token": "Неправильный токен", + "Invalid user": "Недопустимое имя пользователя", + "Token is expired, please try again": "Срок действия токена истек, попробуйте позже", + "English": "Английский", + "English (auto-generated)": "Английский (созданы автоматически)", + "Afrikaans": "Африкаанс", + "Albanian": "Албанский", + "Amharic": "Амхарский", + "Arabic": "Арабский", + "Armenian": "Армянский", + "Azerbaijani": "Азербайджанский", + "Bangla": "Бенгальский", + "Basque": "Баскский", + "Belarusian": "Белорусский", + "Bosnian": "Боснийский", + "Bulgarian": "Болгарский", + "Burmese": "Бирманский", + "Catalan": "Каталонский", + "Cebuano": "Себуанский", + "Chinese (Simplified)": "Китайский (упрощенный)", + "Chinese (Traditional)": "Китайский (традиционный)", + "Corsican": "Корсиканский", + "Croatian": "Хорватский", + "Czech": "Чешский", + "Danish": "Датский", + "Dutch": "Нидерландский", + "Esperanto": "Эсперанто", + "Estonian": "Эстонский", + "Filipino": "Филиппинский", + "Finnish": "Финский", + "French": "Французский", + "Galician": "Галисийский", + "Georgian": "Грузинский", + "German": "Немецкий", + "Greek": "Греческий", + "Gujarati": "Гуджаратский", + "Haitian Creole": "Гаит. креольский", + "Hausa": "Хауса", + "Hawaiian": "Гавайский", + "Hebrew": "Иврит", + "Hindi": "Хинди", + "Hmong": "Хмонг (мяо)", + "Hungarian": "Венгерский", + "Icelandic": "Исландский", + "Igbo": "Игбо", + "Indonesian": "Индонезийский", + "Irish": "Ирландский", + "Italian": "Итальянский", + "Japanese": "Японский", + "Javanese": "Яванский", + "Kannada": "Каннада", + "Kazakh": "Казахский", + "Khmer": "Кхмерский", + "Korean": "Корейский", + "Kurdish": "Курдский", + "Kyrgyz": "Киргизский", + "Lao": "Лаосский", + "Latin": "Латинский", + "Latvian": "Латышский", + "Lithuanian": "Литовский", + "Luxembourgish": "Люксембургский", + "Macedonian": "Македонский", + "Malagasy": "Малагасийский", + "Malay": "Малайский", + "Malayalam": "Малаялам", + "Maltese": "Мальтийский", + "Maori": "Маори", + "Marathi": "Маратхи", + "Mongolian": "Монгольская", + "Nepali": "Непальский", + "Norwegian": "Норвежский", + "Nyanja": "Ньянджа", + "Pashto": "Пушту", + "Persian": "Персидский", + "Polish": "Польский", + "Portuguese": "Португальский", + "Punjabi": "Панджаби", + "Romanian": "Румынский", + "Russian": "Русский", + "Samoan": "Самоанский", + "Scottish Gaelic": "Шотландский (гэльский)", + "Serbian": "Сербский", + "Shona": "Шона", + "Sindhi": "Синдхи", + "Sinhala": "Сингальский", + "Slovak": "Словацкий", + "Slovenian": "Словенский", + "Somali": "Сомалийский", + "Southern Sotho": "Сесото (южный сото)", + "Spanish": "Испанский", + "Spanish (Latin America)": "Испанский (Латинская Америка)", + "Sundanese": "Сунданский", + "Swahili": "Суахили", + "Swedish": "Шведский", + "Tajik": "Таджикский", + "Tamil": "Тамильский", + "Telugu": "Телугу", + "Thai": "Тайский", + "Turkish": "Турецкий", + "Ukrainian": "Украинский", + "Urdu": "Урду", + "Uzbek": "Узбекский", + "Vietnamese": "Вьетнамский", + "Welsh": "Валлийский", + "Western Frisian": "Западнофризский", + "Xhosa": "Коса", + "Yiddish": "Идиш", + "Yoruba": "Йоруба", + "Zulu": "Зулусский", + "`x` years": "`x` лет", + "`x` months": "`x` месяцев", + "`x` weeks": "`x` недель", + "`x` days": "`x` дней", + "`x` hours": "`x` часов", + "`x` minutes": "`x` минут", + "`x` seconds": "`x` секунд", + "Fallback comments: ": "Резервные комментарии: ", + "Popular": "Популярное", + "Top": "Топ", + "About": "О сайте", + "Rating: ": "Рейтинг: ", + "Language: ": "Язык: ", + "Default": "По-умолчанию", + "Music": "Музыка", + "Gaming": "Игры", + "News": "Новости", + "Movies": "Фильмы", + "Download": "Скачать", + "Download as: ": "Скачать как: ", + "%A %B %-d, %Y": "%-d %B %Y, %A", + "(edited)": "(изменено)", + "Youtube permalink of the comment": "Прямая ссылка на YouTube", + "`x` marked it with a ❤": "❤ от автора канала \"`x`\"", + "Audio mode": "Аудио режим", + "Video mode": "Видео режим" } diff --git a/src/invidious.cr b/src/invidious.cr index e6e8340b..b6356a51 100644 --- a/src/invidious.cr +++ b/src/invidious.cr @@ -31,42 +31,38 @@ require "./invidious/*" CONFIG = Config.from_yaml(File.read("config/config.yml")) HMAC_KEY = CONFIG.hmac_key || Random::Secure.random_bytes(32) -crawl_threads = CONFIG.crawl_threads -channel_threads = CONFIG.channel_threads -feed_threads = CONFIG.feed_threads -video_threads = CONFIG.video_threads - +config = CONFIG logger = Invidious::LogHandler.new Kemal.config.extra_options do |parser| parser.banner = "Usage: invidious [arguments]" - parser.on("-t THREADS", "--crawl-threads=THREADS", "Number of threads for crawling YouTube (default: #{crawl_threads})") do |number| + parser.on("-t THREADS", "--crawl-threads=THREADS", "Number of threads for crawling YouTube (default: #{config.crawl_threads})") do |number| begin - crawl_threads = number.to_i + config.crawl_threads = number.to_i rescue ex puts "THREADS must be integer" exit end end - parser.on("-c THREADS", "--channel-threads=THREADS", "Number of threads for refreshing channels (default: #{channel_threads})") do |number| + parser.on("-c THREADS", "--channel-threads=THREADS", "Number of threads for refreshing channels (default: #{config.channel_threads})") do |number| begin - channel_threads = number.to_i + config.channel_threads = number.to_i rescue ex puts "THREADS must be integer" exit end end - parser.on("-f THREADS", "--feed-threads=THREADS", "Number of threads for refreshing feeds (default: #{feed_threads})") do |number| + parser.on("-f THREADS", "--feed-threads=THREADS", "Number of threads for refreshing feeds (default: #{config.feed_threads})") do |number| begin - feed_threads = number.to_i + config.feed_threads = number.to_i rescue ex puts "THREADS must be integer" exit end end - parser.on("-v THREADS", "--video-threads=THREADS", "Number of threads for refreshing videos (default: #{video_threads})") do |number| + parser.on("-v THREADS", "--video-threads=THREADS", "Number of threads for refreshing videos (default: #{config.video_threads})") do |number| begin - video_threads = number.to_i + config.video_threads = number.to_i rescue ex puts "THREADS must be integer" exit @@ -94,6 +90,8 @@ YT_URL = URI.parse("https://www.youtube.com") REDDIT_URL = URI.parse("https://www.reddit.com") LOGIN_URL = URI.parse("https://accounts.google.com") TEXTCAPTCHA_URL = URI.parse("http://textcaptcha.com/omarroth@hotmail.com.json") +CURRENT_COMMIT = `git rev-list HEAD --max-count=1 --abbrev-commit`.strip +CURRENT_VERSION = `git describe --tags $(git rev-list --tags --max-count=1)`.strip LOCALES = { "ar" => load_locale("ar"), @@ -107,28 +105,36 @@ LOCALES = { "ru" => load_locale("ru"), } -crawl_threads.times do +config.crawl_threads.times do spawn do crawl_videos(PG_DB, logger) end end -refresh_channels(PG_DB, logger, channel_threads, CONFIG.full_refresh) +refresh_channels(PG_DB, logger, config.channel_threads, config.full_refresh) -refresh_feeds(PG_DB, logger, feed_threads) +refresh_feeds(PG_DB, logger, config.feed_threads) -video_threads.times do |i| +config.video_threads.times do |i| spawn do refresh_videos(PG_DB, logger) end end +# stats = Statistics.new +# if config.statistics +# spawn do +# end +# end + top_videos = [] of Video -spawn do - pull_top_videos(CONFIG, PG_DB) do |videos| - top_videos = videos - sleep 1.minutes - Fiber.yield +if config.top_enabled + spawn do + pull_top_videos(config, PG_DB) do |videos| + top_videos = videos + sleep 1.minutes + Fiber.yield + end end end @@ -231,7 +237,20 @@ get "/" do |env| end end - templated "index" + case config.default_home + when "Popular" + templated "popular" + when "Top" + templated "top" + when "Trending" + env.redirect "/feed/trending" + when "Subscriptions" + if user + env.redirect "/feed/subscriptions" + else + templated "popular" + end + end end get "/licenses" do |env| @@ -367,7 +386,7 @@ get "/watch" do |env| video.description = replace_links(video.description) description = video.short_description - host_url = make_host_url(Kemal.config.ssl || CONFIG.https_only, CONFIG.domain) + host_url = make_host_url(Kemal.config.ssl || config.https_only, config.domain) host_params = env.request.query_params host_params.delete_all("v") @@ -467,7 +486,7 @@ get "/embed/:id" do |env| video.description = replace_links(video.description) description = video.short_description - host_url = make_host_url(Kemal.config.ssl || CONFIG.https_only, CONFIG.domain) + host_url = make_host_url(Kemal.config.ssl || config.https_only, config.domain) host_params = env.request.query_params host_params.delete_all("v") @@ -553,7 +572,7 @@ get "/opensearch.xml" do |env| locale = LOCALES[env.get("locale").as(String)]? env.response.content_type = "application/opensearchdescription+xml" - host = make_host_url(Kemal.config.ssl || CONFIG.https_only, CONFIG.domain) + host = make_host_url(Kemal.config.ssl || config.https_only, config.domain) XML.build(indent: " ", encoding: "UTF-8") do |xml| xml.element("OpenSearchDescription", xmlns: "http://a9.com/-/spec/opensearch/1.1/") do @@ -678,6 +697,11 @@ get "/login" do |env| next env.redirect "/feed/subscriptions" end + if !config.login_enabled + error_message = "Login has been disabled by administrator." + next templated "error" + end + referer = get_referer(env, "/feed/subscriptions") account_type = env.params.query["type"]? @@ -716,6 +740,11 @@ post "/login" do |env| referer = get_referer(env, "/feed/subscriptions") + if !config.login_enabled + error_message = "Login has been disabled by administrator." + next templated "error" + end + email = env.params.body["email"]? password = env.params.body["password"]? @@ -876,14 +905,14 @@ post "/login" do |env| host = URI.parse(env.request.headers["Host"]).host - if Kemal.config.ssl || CONFIG.https_only + if Kemal.config.ssl || config.https_only secure = true else secure = false end login.cookies.each do |cookie| - if Kemal.config.ssl || CONFIG.https_only + if Kemal.config.ssl || config.https_only cookie.secure = secure else cookie.secure = secure @@ -912,54 +941,56 @@ post "/login" do |env| answer = env.params.body["answer"]? text_answer = env.params.body["text_answer"]? - if answer - answer = answer.lstrip('0') - answer = OpenSSL::HMAC.hexdigest(:sha256, HMAC_KEY, answer) - - challenge = env.params.body["challenge"]? - token = env.params.body["token"]? - - begin - validate_response(challenge, token, answer, "sign_in", HMAC_KEY, PG_DB, locale) - rescue ex - if ex.message == translate(locale, "Invalid user") - error_message = translate(locale, "Invalid answer") - else - error_message = ex.message - end - - next templated "error" - end - elsif text_answer - text_answer = Digest::MD5.hexdigest(text_answer.downcase.strip) + if config.captcha_enabled + if answer + answer = answer.lstrip('0') + answer = OpenSSL::HMAC.hexdigest(:sha256, HMAC_KEY, answer) - challenges = env.params.body.select { |k, v| k.match(/text_challenge\d+/) } - tokens = env.params.body.select { |k, v| k.match(/text_token\d+/) } + challenge = env.params.body["challenge"]? + token = env.params.body["token"]? - found_valid_captcha = false - - error_message = translate(locale, "Invalid CAPTCHA") - challenges.each_with_index do |challenge, i| begin - challenge = challenge[1] - token = tokens[i][1] - validate_response(challenge, token, text_answer, "sign_in", HMAC_KEY, PG_DB, locale) - found_valid_captcha = true + validate_response(challenge, token, answer, "sign_in", HMAC_KEY, PG_DB, locale) rescue ex if ex.message == translate(locale, "Invalid user") error_message = translate(locale, "Invalid answer") else error_message = ex.message end + + next templated "error" + end + elsif text_answer + text_answer = Digest::MD5.hexdigest(text_answer.downcase.strip) + + challenges = env.params.body.select { |k, v| k.match(/text_challenge\d+/) } + tokens = env.params.body.select { |k, v| k.match(/text_token\d+/) } + + found_valid_captcha = false + + error_message = translate(locale, "Invalid CAPTCHA") + challenges.each_with_index do |challenge, i| + begin + challenge = challenge[1] + token = tokens[i][1] + validate_response(challenge, token, text_answer, "sign_in", HMAC_KEY, PG_DB, locale) + found_valid_captcha = true + rescue ex + if ex.message == translate(locale, "Invalid user") + error_message = translate(locale, "Invalid answer") + else + error_message = ex.message + end + end end - end - if !found_valid_captcha + if !found_valid_captcha + next templated "error" + end + else + error_message = translate(locale, "CAPTCHA is a required field") next templated "error" end - else - error_message = translate(locale, "CAPTCHA is a required field") - next templated "error" end action = env.params.body["action"]? @@ -992,14 +1023,14 @@ post "/login" do |env| sid = Base64.urlsafe_encode(Random::Secure.random_bytes(32)) PG_DB.exec("INSERT INTO session_ids VALUES ($1, $2, $3)", sid, email, Time.now) - if Kemal.config.ssl || CONFIG.https_only + if Kemal.config.ssl || config.https_only secure = true else secure = false end - if CONFIG.domain - env.response.cookies["SID"] = HTTP::Cookie.new(name: "SID", domain: ".#{CONFIG.domain}", value: sid, expires: Time.now + 2.years, + if config.domain + env.response.cookies["SID"] = HTTP::Cookie.new(name: "SID", domain: ".#{config.domain}", value: sid, expires: Time.now + 2.years, secure: secure, http_only: true) else env.response.cookies["SID"] = HTTP::Cookie.new(name: "SID", value: sid, expires: Time.now + 2.years, @@ -1016,6 +1047,11 @@ post "/login" do |env| secure: secure, http_only: true) end elsif action == "register" + if !config.registration_enabled + error_message = "Registration has been disabled by administrator." + next templated "error" + end + if password.empty? error_message = translate(locale, "Password cannot be empty") next templated "error" @@ -1049,14 +1085,14 @@ post "/login" do |env| ucid = ANY ((SELECT subscriptions FROM users WHERE email = E'#{user.email.gsub("'", "\\'")}')::text[]) \ ORDER BY published DESC;") - if Kemal.config.ssl || CONFIG.https_only + if Kemal.config.ssl || config.https_only secure = true else secure = false end - if CONFIG.domain - env.response.cookies["SID"] = HTTP::Cookie.new(name: "SID", domain: ".#{CONFIG.domain}", value: sid, expires: Time.now + 2.years, + if config.domain + env.response.cookies["SID"] = HTTP::Cookie.new(name: "SID", domain: ".#{config.domain}", value: sid, expires: Time.now + 2.years, secure: secure, http_only: true) else env.response.cookies["SID"] = HTTP::Cookie.new(name: "SID", value: sid, expires: Time.now + 2.years, @@ -1153,14 +1189,15 @@ post "/preferences" do |env| volume = env.params.body["volume"]?.try &.as(String).to_i? volume ||= DEFAULT_USER_PREFERENCES.volume - comments_0 = env.params.body["comments_0"]?.try &.as(String) || DEFAULT_USER_PREFERENCES.comments[0] - comments_1 = env.params.body["comments_1"]?.try &.as(String) || DEFAULT_USER_PREFERENCES.comments[1] - comments = [comments_0, comments_1] + comments = [] of String + 2.times do |i| + comments << (env.params.body["comments[#{i}]"]?.try &.as(String) || DEFAULT_USER_PREFERENCES.comments[i]) + end - captions_0 = env.params.body["captions_0"]?.try &.as(String) || DEFAULT_USER_PREFERENCES.captions[0] - captions_1 = env.params.body["captions_1"]?.try &.as(String) || DEFAULT_USER_PREFERENCES.captions[1] - captions_2 = env.params.body["captions_2"]?.try &.as(String) || DEFAULT_USER_PREFERENCES.captions[2] - captions = [captions_0, captions_1, captions_2] + captions = [] of String + 3.times do |i| + captions << (env.params.body["captions[#{i}]"]?.try &.as(String) || DEFAULT_USER_PREFERENCES.captions[i]) + end related_videos = env.params.body["related_videos"]?.try &.as(String) related_videos ||= "off" @@ -1224,6 +1261,37 @@ post "/preferences" do |env| if user = env.get? "user" user = user.as(User) PG_DB.exec("UPDATE users SET preferences = $1 WHERE email = $2", preferences, user.email) + + if config.admins.includes? user.email + config.default_home = env.params.body["default_home"]?.try &.as(String) || config.default_home + + feed_menu = [] of String + 4.times do |index| + option = env.params.body["feed_menu[#{index}]"]?.try &.as(String) || "" + if !option.empty? + feed_menu << option + end + end + config.feed_menu = feed_menu + + top_enabled = env.params.body["top_enabled"]?.try &.as(String) + top_enabled ||= "off" + config.top_enabled = top_enabled == "on" + + captcha_enabled = env.params.body["captcha_enabled"]?.try &.as(String) + captcha_enabled ||= "off" + config.captcha_enabled = captcha_enabled == "on" + + login_enabled = env.params.body["login_enabled"]?.try &.as(String) + login_enabled ||= "off" + config.login_enabled = login_enabled == "on" + + registration_enabled = env.params.body["registration_enabled"]?.try &.as(String) + registration_enabled ||= "off" + config.registration_enabled = registration_enabled == "on" + + File.write("config/config.yml", config.to_yaml) + end else env.response.cookies["PREFS"] = preferences end @@ -1397,7 +1465,7 @@ get "/subscription_manager" do |env| subscriptions.sort_by! { |channel| channel.author.downcase } if action_takeout - host_url = make_host_url(Kemal.config.ssl || CONFIG.https_only, CONFIG.domain) + host_url = make_host_url(Kemal.config.ssl || config.https_only, config.domain) if format == "json" env.response.content_type = "application/json" @@ -1741,7 +1809,11 @@ end get "/feed/top" do |env| locale = LOCALES[env.get("locale").as(String)]? - templated "top" + if config.top_enabled + templated "top" + else + env.redirect "/" + end end get "/feed/popular" do |env| @@ -1984,7 +2056,7 @@ get "/feed/channel/:ucid" do |env| ) end - host_url = make_host_url(Kemal.config.ssl || CONFIG.https_only, CONFIG.domain) + host_url = make_host_url(Kemal.config.ssl || config.https_only, config.domain) path = env.request.path feed = XML.build(indent: " ", encoding: "UTF-8") do |xml| @@ -2118,7 +2190,7 @@ get "/feed/private" do |env| videos = videos[0..max_results] end - host_url = make_host_url(Kemal.config.ssl || CONFIG.https_only, CONFIG.domain) + host_url = make_host_url(Kemal.config.ssl || config.https_only, config.domain) path = env.request.path query = env.request.query.not_nil! @@ -2173,7 +2245,7 @@ get "/feed/playlist/:plid" do |env| plid = env.params.url["plid"] - host_url = make_host_url(Kemal.config.ssl || CONFIG.https_only, CONFIG.domain) + host_url = make_host_url(Kemal.config.ssl || config.https_only, config.domain) path = env.request.path client = make_client(YT_URL) @@ -2487,7 +2559,7 @@ get "/api/v1/insights/:id" do |env| env.response.content_type = "application/json" error_message = {"error" => "YouTube has removed publicly-available analytics."}.to_json - halt env, status_code: 503, response: error_message + halt env, status_code: 410, response: error_message client = make_client(YT_URL) headers = HTTP::Headers.new @@ -2653,7 +2725,7 @@ get "/api/v1/videos/:id" do |env| end if video.player_response["streamingData"]?.try &.["hlsManifestUrl"]? - host_url = make_host_url(Kemal.config.ssl || CONFIG.https_only, CONFIG.domain) + host_url = make_host_url(Kemal.config.ssl || config.https_only, config.domain) host_params = env.request.query_params host_params.delete_all("v") @@ -2871,6 +2943,11 @@ get "/api/v1/top" do |env| env.response.content_type = "application/json" + if !config.top_enabled + error_message = {"error" => "Administrator has disabled this endpoint."}.to_json + halt env, status_code: 400, response: error_message + end + videos = JSON.build do |json| json.array do top_videos.each do |video| @@ -3842,7 +3919,7 @@ get "/api/manifest/hls_variant/*" do |env| env.response.content_type = "application/x-mpegURL" env.response.headers.add("Access-Control-Allow-Origin", "*") - host_url = make_host_url(Kemal.config.ssl || CONFIG.https_only, CONFIG.domain) + host_url = make_host_url(Kemal.config.ssl || config.https_only, config.domain) manifest = manifest.body manifest.gsub("https://www.youtube.com", host_url) @@ -3856,7 +3933,7 @@ get "/api/manifest/hls_playlist/*" do |env| halt env, status_code: manifest.status_code end - host_url = make_host_url(Kemal.config.ssl || CONFIG.https_only, CONFIG.domain) + host_url = make_host_url(Kemal.config.ssl || config.https_only, config.domain) manifest = manifest.body.gsub("https://www.youtube.com", host_url) manifest = manifest.gsub(/https:\/\/r\d---.{11}\.c\.youtube\.com/, host_url) diff --git a/src/invidious/helpers/helpers.cr b/src/invidious/helpers/helpers.cr index 45ebc4dd..26b6244c 100644 --- a/src/invidious/helpers/helpers.cr +++ b/src/invidious/helpers/helpers.cr @@ -1,9 +1,9 @@ class Config YAML.mapping({ + video_threads: Int32, # Number of threads to use for updating videos in cache (mostly non-functional) crawl_threads: Int32, # Number of threads to use for finding new videos from YouTube (used to populate "top" page) channel_threads: Int32, # Number of threads to use for crawling videos from channels (for updating subscriptions) feed_threads: Int32, # Number of threads to use for updating feeds - video_threads: Int32, # Number of threads to use for updating videos in cache (mostly non-functional) db: NamedTuple( # Database configuration user: String, password: String, @@ -11,11 +11,18 @@ user: String, port: Int32, dbname: String, ), - dl_api_key: String?, # DetectLanguage API Key (used to filter non-English results from "top" page), mostly non-functional - https_only: Bool?, # Used to tell Invidious it is behind a proxy, so links to resources should be https:// - hmac_key: String?, # HMAC signing key for CSRF tokens - full_refresh: Bool, # Used for crawling channels: threads should check all videos uploaded by a channel - domain: String, # Domain to be used for links to resources on the site where an absolute URL is required + full_refresh: Bool, # Used for crawling channels: threads should check all videos uploaded by a channel + https_only: Bool?, # Used to tell Invidious it is behind a proxy, so links to resources should be https:// + hmac_key: String?, # HMAC signing key for CSRF tokens + domain: String, # Domain to be used for links to resources on the site where an absolute URL is required + dl_api_key: String?, # DetectLanguage API Key (used to filter non-English results from "top" page), mostly non-functional + default_home: {type: String, default: "Top"}, + feed_menu: {type: Array(String), default: ["Popular", "Top", "Trending"]}, + top_enabled: {type: Bool, default: true}, + captcha_enabled: {type: Bool, default: true}, + login_enabled: {type: Bool, default: true}, + registration_enabled: {type: Bool, default: true}, + admins: {type: Array(String), default: [] of String}, }) end diff --git a/src/invidious/jobs.cr b/src/invidious/jobs.cr index e000540a..cc5f85d1 100644 --- a/src/invidious/jobs.cr +++ b/src/invidious/jobs.cr @@ -133,7 +133,7 @@ def refresh_feeds(db, logger, max_threads = 1) rescue ex # Create view if it doesn't exist if ex.message.try &.ends_with? "does not exist" - PG_DB.exec("CREATE MATERIALIZED VIEW #{view_name} AS \ + db.exec("CREATE MATERIALIZED VIEW #{view_name} AS \ SELECT * FROM channel_videos WHERE \ ucid = ANY ((SELECT subscriptions FROM users WHERE email = E'#{email.gsub("'", "\\'")}')::text[]) \ ORDER BY published DESC;") @@ -193,11 +193,11 @@ end def pull_popular_videos(db) loop do - subscriptions = PG_DB.query_all("SELECT channel FROM \ + subscriptions = db.query_all("SELECT channel FROM \ (SELECT UNNEST(subscriptions) AS channel FROM users) AS d \ GROUP BY channel ORDER BY COUNT(channel) DESC LIMIT 40", as: String) - videos = PG_DB.query_all("SELECT DISTINCT ON (ucid) * FROM \ + videos = db.query_all("SELECT DISTINCT ON (ucid) * FROM \ channel_videos WHERE ucid IN (#{arg_array(subscriptions)}) \ ORDER BY ucid, published DESC", subscriptions, as: ChannelVideo).sort_by { |video| video.published }.reverse diff --git a/src/invidious/users.cr b/src/invidious/users.cr index 48d8008f..42468228 100644 --- a/src/invidious/users.cr +++ b/src/invidious/users.cr @@ -143,7 +143,7 @@ def get_user(sid, headers, db, refresh = true) begin view_name = "subscriptions_#{sha256(user.email)[0..7]}" - PG_DB.exec("CREATE MATERIALIZED VIEW #{view_name} AS \ + db.exec("CREATE MATERIALIZED VIEW #{view_name} AS \ SELECT * FROM channel_videos WHERE \ ucid = ANY ((SELECT subscriptions FROM users WHERE email = E'#{user.email.gsub("'", "\\'")}')::text[]) \ ORDER BY published DESC;") @@ -165,7 +165,7 @@ def get_user(sid, headers, db, refresh = true) begin view_name = "subscriptions_#{sha256(user.email)[0..7]}" - PG_DB.exec("CREATE MATERIALIZED VIEW #{view_name} AS \ + db.exec("CREATE MATERIALIZED VIEW #{view_name} AS \ SELECT * FROM channel_videos WHERE \ ucid = ANY ((SELECT subscriptions FROM users WHERE email = E'#{user.email.gsub("'", "\\'")}')::text[]) \ ORDER BY published DESC;") @@ -247,7 +247,7 @@ def validate_response(challenge, token, user_id, operation, key, db, locale) raise translate(locale, "Invalid challenge") end - challenge = OpenSSL::HMAC.digest(:sha256, HMAC_KEY, challenge) + challenge = OpenSSL::HMAC.digest(:sha256, key, challenge) challenge = Base64.urlsafe_encode(challenge) if db.query_one?("SELECT EXISTS (SELECT true FROM nonces WHERE nonce = $1)", nonce, as: Bool) diff --git a/src/invidious/views/components/feed_menu.ecr b/src/invidious/views/components/feed_menu.ecr index 5c19bafc..378601a1 100644 --- a/src/invidious/views/components/feed_menu.ecr +++ b/src/invidious/views/components/feed_menu.ecr @@ -2,12 +2,12 @@ <div class="pure-u-1 pure-u-md-1-4"></div> <div class="pure-u-1 pure-u-md-1-2"> <div class="pure-g"> - <% feeds = ["Popular", "Top", "Trending"] %> - <% if env.get? "user" %> - <% feeds << "Subscriptions" %> + <% feed_menu = config.feed_menu.dup %> + <% if !env.get?("user") %> + <% feed_menu.reject! {|feed| feed == "Subscriptions"} %> <% end %> - <% feeds.each do |feed| %> - <div class="pure-u-1-2 pure-u-md-1-<%= feeds.size %>"> + <% feed_menu.each do |feed| %> + <div class="pure-u-1-2 pure-u-md-1-<%= feed_menu.size %>"> <a href="/feed/<%= feed.downcase %>" style="text-align:center;" class="pure-menu-heading"> <%= translate(locale, feed) %> </a> diff --git a/src/invidious/views/index.ecr b/src/invidious/views/index.ecr deleted file mode 100644 index 6cc978e5..00000000 --- a/src/invidious/views/index.ecr +++ /dev/null @@ -1,14 +0,0 @@ -<% content_for "header" do %> -<meta name="description" content="<%= translate(locale, "An alternative front-end to YouTube") %>"> -<title>Invidious</title> -<% end %> - -<%= rendered "components/feed_menu" %> - -<div class="pure-g"> -<% top_videos.each_slice(4) do |slice| %> - <% slice.each do |item| %> - <%= rendered "components/item" %> - <% end %> -<% end %> -</div> diff --git a/src/invidious/views/login.ecr b/src/invidious/views/login.ecr index f4b0ce96..20363b2c 100644 --- a/src/invidious/views/login.ecr +++ b/src/invidious/views/login.ecr @@ -27,36 +27,40 @@ <label for="password"><%= translate(locale, "Password:") %></label> <input required class="pure-input-1" name="password" type="password" placeholder="Password"> - - <% if captcha_type == "image" %> - <img style="width:100%" src='<%= captcha.not_nil![:image] %>'/> - <input type="hidden" name="token" value="<%= captcha.not_nil![:token] %>"> - <input type="hidden" name="challenge" value="<%= captcha.not_nil![:challenge] %>"> - <label for="answer"><%= translate(locale, "Time (h:mm:ss):") %></label> - <input required type="text" name="answer" type="text" placeholder="h:mm:ss"> - - <label> - <a href="/login?referer=<%= URI.escape(referer) %>&type=invidious&captcha=text"> - <%= translate(locale, "Text CAPTCHA") %> - </a> - </label> - <% else %> - <% text_captcha.not_nil![:tokens].each_with_index do |token, i| %> - <input type="hidden" name="text_challenge<%= i %>" value="<%= token[0] %>"> - <input type="hidden" name="text_token<%= i %>" value="<%= token[1] %>"> - <% end %> - <label for="text_answer"><%= text_captcha.not_nil![:question] %></label> - <input required type="text" name="text_answer" type="text" placeholder="Answer"> - <label> - <a href="/login?referer=<%= URI.escape(referer) %>&type=invidious"> - <%= translate(locale, "Image CAPTCHA") %> - </a> - </label> + <% if config.captcha_enabled %> + <% if captcha_type == "image" %> + <img style="width:100%" src='<%= captcha.not_nil![:image] %>'/> + <input type="hidden" name="token" value="<%= captcha.not_nil![:token] %>"> + <input type="hidden" name="challenge" value="<%= captcha.not_nil![:challenge] %>"> + <label for="answer"><%= translate(locale, "Time (h:mm:ss):") %></label> + <input required type="text" name="answer" type="text" placeholder="h:mm:ss"> + + <label> + <a href="/login?referer=<%= URI.escape(referer) %>&type=invidious&captcha=text"> + <%= translate(locale, "Text CAPTCHA") %> + </a> + </label> + <% else %> + <% text_captcha.not_nil![:tokens].each_with_index do |token, i| %> + <input type="hidden" name="text_challenge<%= i %>" value="<%= token[0] %>"> + <input type="hidden" name="text_token<%= i %>" value="<%= token[1] %>"> + <% end %> + <label for="text_answer"><%= text_captcha.not_nil![:question] %></label> + <input required type="text" name="text_answer" type="text" placeholder="Answer"> + + <label> + <a href="/login?referer=<%= URI.escape(referer) %>&type=invidious"> + <%= translate(locale, "Image CAPTCHA") %> + </a> + </label> + <% end %> <% end %> <button type="submit" name="action" value="signin" class="pure-button pure-button-primary"><%= translate(locale, "Sign In") %></button> + <% if config.registration_enabled %> <button type="submit" name="action" value="register" class="pure-button pure-button-primary"><%= translate(locale, "Register") %></button> + <% end %> </fieldset> </form> <% elsif account_type == "google" %> @@ -67,7 +71,7 @@ <label for="password"><%= translate(locale, "Password:") %></label> <input required class="pure-input-1" name="password" type="password" placeholder="Password"> - + <% if tfa %> <label for="tfa"><%= translate(locale, "Google verification code:") %></label> <input required class="pure-input-1" name="tfa" type="text" placeholder="Google verification code"> diff --git a/src/invidious/views/popular.ecr b/src/invidious/views/popular.ecr index f235aad8..d60ae557 100644 --- a/src/invidious/views/popular.ecr +++ b/src/invidious/views/popular.ecr @@ -1,6 +1,6 @@ <% content_for "header" do %> <meta name="description" content="<%= translate(locale, "An alternative front-end to YouTube") %>"> -<title><%= translate(locale, "Popular") %> - Invidious</title> +<title><% if config.default_home != "Popular" %><%= translate(locale, "Popular") %> - <% end %>Invidious</title> <% end %> <%= rendered "components/feed_menu" %> diff --git a/src/invidious/views/preferences.ecr b/src/invidious/views/preferences.ecr index 7435c91e..ea6a84c8 100644 --- a/src/invidious/views/preferences.ecr +++ b/src/invidious/views/preferences.ecr @@ -58,45 +58,25 @@ function update_value(element) { </div> <div class="pure-control-group"> - <label for="comments_0"><%= translate(locale, "Default comments: ") %></label> - <select name="comments_0" id="comments_0"> + <label for="comments[0]"><%= translate(locale, "Default comments: ") %></label> + <% preferences.comments.each_with_index do |comments, index| %> + <select name="comments[<%= index %>]" id="comments[<%= index %>]"> <% {"", "youtube", "reddit"}.each do |option| %> - <option value="<%= option %>" <% if preferences.comments[0] == option %> selected <% end %>><%= translate(locale, option) %></option> + <option value="<%= option %>" <% if preferences.comments[index] == option %> selected <% end %>><%= translate(locale, option) %></option> <% end %> </select> - </div> - - <div class="pure-control-group"> - <label for="comments_1"><%= translate(locale, "Fallback comments: ") %></label> - <select name="comments_1" id="comments_1"> - <% {"", "youtube", "reddit"}.each do |option| %> - <option value="<%= option %>" <% if preferences.comments[1] == option %> selected <% end %>><%= translate(locale, option) %></option> - <% end %> - </select> - </div> - - <div class="pure-control-group"> - <label for="captions_0"><%= translate(locale, "Default captions: ") %></label> - <select class="pure-u-1-5" name="captions_0" id="captions_0"> - <% CAPTION_LANGUAGES.each do |option| %> - <option value="<%= option %>" <% if preferences.captions[0] == option %> selected <% end %>><%= translate(locale, option) %></option> <% end %> - </select> </div> <div class="pure-control-group"> - <label for="captions_fallback"><%= translate(locale, "Fallback captions: ") %></label> - <select class="pure-u-1-5" name="captions_1" id="captions_1"> + <label for="captions[0]"><%= translate(locale, "Default captions: ") %></label> + <% preferences.captions.each_with_index do |caption, index| %> + <select class="pure-u-1-6" name="captions[<%= index %>]" id="captions[<%= index %>]"> <% CAPTION_LANGUAGES.each do |option| %> - <option value="<%= option %>" <% if preferences.captions[1] == option %> selected <% end %>><%= translate(locale, option) %></option> + <option value="<%= option %>" <% if preferences.captions[index] == option %> selected <% end %>><%= translate(locale, option) %></option> <% end %> </select> - - <select class="pure-u-1-5" name="captions_2" id="captions_2"> - <% CAPTION_LANGUAGES.each do |option| %> - <option value="<%= option %>" <% if preferences.captions[2] == option %> selected <% end %>><%= translate(locale, option) %></option> <% end %> - </select> </div> <div class="pure-control-group"> @@ -167,13 +147,57 @@ function update_value(element) { </div> <% end %> + <% if env.get?("user") && config.admins.includes? env.get?("user").as(User).email %> + <legend><%= translate(locale, "Administrator preferences") %></legend> + + <div class="pure-control-group"> + <label for="default_home"><%= translate(locale, "Default homepage: ") %></label> + <select name="default_home" id="default_home"> + <% {"Popular", "Top", "Trending", "Subscriptions"}.each do |option| %> + <option value="<%= option %>" <% if config.default_home == option %> selected <% end %>><%= translate(locale, option) %></option> + <% end %> + </select> + </div> + + <div class="pure-control-group"> + <label for="feed_menu"><%= translate(locale, "Feed menu: ") %></label> + <% 4.times do |index| %> + <select name="feed_menu[<%= index %>]" id="feed_menu[<%= index %>]"> + <% {"", "Popular", "Top", "Trending", "Subscriptions"}.each do |option| %> + <option value="<%= option %>" <% if config.feed_menu[index]? == option %> selected <% end %>><%= translate(locale, option) %></option> + <% end %> + </select> + <% end %> + </div> + + <div class="pure-control-group"> + <label for="top_enabled"><%= translate(locale, "Top enabled? ") %></label> + <input name="top_enabled" id="top_enabled" type="checkbox" <% if config.top_enabled %>checked<% end %>> + </div> + + <div class="pure-control-group"> + <label for="captcha_enabled"><%= translate(locale, "CAPTCHA enabled? ") %></label> + <input name="captcha_enabled" id="captcha_enabled" type="checkbox" <% if config.captcha_enabled %>checked<% end %>> + </div> + + <div class="pure-control-group"> + <label for="login_enabled"><%= translate(locale, "Login enabled? ") %></label> + <input name="login_enabled" id="login_enabled" type="checkbox" <% if config.login_enabled %>checked<% end %>> + </div> + + <div class="pure-control-group"> + <label for="registration_enabled"><%= translate(locale, "Registration enabled? ") %></label> + <input name="registration_enabled" id="registration_enabled" type="checkbox" <% if config.registration_enabled %>checked<% end %>> + </div> + <% end %> + <% if env.get? "user" %> <legend><%= translate(locale, "Data preferences") %></legend> <div class="pure-control-group"> <a href="/clear_watch_history?referer=<%= URI.escape(referer) %>"><%= translate(locale, "Clear watch history") %></a> </div> - + <div class="pure-control-group"> <a href="/data_control?referer=<%= URI.escape(referer) %>"><%= translate(locale, "Import/Export data") %></a> </div> diff --git a/src/invidious/views/subscriptions.ecr b/src/invidious/views/subscriptions.ecr index 109da18f..f1b72aaa 100644 --- a/src/invidious/views/subscriptions.ecr +++ b/src/invidious/views/subscriptions.ecr @@ -72,7 +72,7 @@ function mark_watched(target) { } </script> -<div class="pure-g"> +<div class="pure-g h-box"> <div class="pure-u-1 pure-u-md-1-5"> <% if page >= 2 %> <a href="/feed/subscriptions?max_results=<%= max_results %>&page=<%= page - 1 %>"> diff --git a/src/invidious/views/template.ecr b/src/invidious/views/template.ecr index b58135d7..7e5f4809 100644 --- a/src/invidious/views/template.ecr +++ b/src/invidious/views/template.ecr @@ -89,43 +89,41 @@ <i class="icon ion-ios-cog"></i> </a> </div> + <% if config.login_enabled %> <div class="pure-u-1-3"> <a href="/login?referer=<%= env.get?("current_page") %>" class="pure-menu-heading"> <%= translate(locale, "Login") %> </a> </div> <% end %> + <% end %> </div> </div> <%= content %> + <div class="h-box"> + <hr> + </div> <div class="footer"> - <p> - <a href="https://github.com/omarroth"> - <%= translate(locale, "Released under the AGPLv3 by Omar Roth.") %> - </a> - </p> - <p> - <a href="https://github.com/omarroth/invidious"> - <%= translate(locale, "Source available here.") %> - </a> - </p> - <p><%= translate(locale, "Liberapay: ") %> - <a href="https://liberapay.com/omarroth"> - https://liberapay.com/omarroth - </a> - </p> - <p><%= translate(locale, "Patreon: ") %> - <a href="https://patreon.com/omarroth"> - https://patreon.com/omarroth - </a> - </p> - <p><%= translate(locale, "BTC: ") %>356DpZyMXu6rYd55Yqzjs29n79kGKWcYrY</p> - <p><%= translate(locale, "BCH: ") %>qq4ptclkzej5eza6a50et5ggc58hxsq5aylqut2npk</p> - <p> - <a rel="jslicense" href="/licenses"> - <%= translate(locale, "View JavaScript license information.") %> - </a> - </p> + <div class="pure-g"> + <div class="pure-u-1 pure-u-md-1-3"> + <a href="https://github.com/omarroth/invidious"> + <%= translate(locale, "Released under the AGPLv3 by Omar Roth.") %> + </a> + </div> + <div class="pure-u-1 pure-u-md-1-3"><%= translate(locale, "BTC: ") %>356DpZyMXu6rYd55Yqzjs29n79kGKWcYrY</div> + <div class="pure-u-1 pure-u-md-1-3"><%= translate(locale, "BCH: ") %>qq4ptclkzej5eza6a50et5ggc58hxsq5aylqut2npk</div> + <div class="pure-u-1 pure-u-md-1-3"> + <a href="https://liberapay.com/omarroth"><%= translate(locale, "Liberapay") %></a> + / + <a href="https://patreon.com/omarroth"><%= translate(locale, "Patreon") %></a> + </div> + <div class="pure-u-1 pure-u-md-1-3"> + <a rel="jslicense" href="/licenses"> + <%= translate(locale, "View JavaScript license information.") %> + </a> + </div> + <div class="pure-u-1 pure-u-md-1-3"><%= translate(locale, "Current version: ") %> <%= CURRENT_VERSION %>-<%= CURRENT_COMMIT %></div> + </div> </div> </div> <div class="pure-u-1 pure-u-md-2-24"></div> diff --git a/src/invidious/views/top.ecr b/src/invidious/views/top.ecr index acf122a6..44faf706 100644 --- a/src/invidious/views/top.ecr +++ b/src/invidious/views/top.ecr @@ -1,6 +1,6 @@ <% content_for "header" do %> <meta name="description" content="<%= translate(locale, "An alternative front-end to YouTube") %>"> -<title><%= translate(locale, "Top") %> - Invidious</title> +<title><% if config.default_home != "Top" %><%= translate(locale, "Top") %> - <% end %>Invidious</title> <% end %> <%= rendered "components/feed_menu" %> diff --git a/src/invidious/views/trending.ecr b/src/invidious/views/trending.ecr index 617a9a58..efd9999a 100644 --- a/src/invidious/views/trending.ecr +++ b/src/invidious/views/trending.ecr @@ -1,6 +1,6 @@ <% content_for "header" do %> <meta name="description" content="<%= translate(locale, "An alternative front-end to YouTube") %>"> -<title><%= translate(locale, "Trending") %> - Invidious</title> +<title><% if config.default_home != "Trending" %><%= translate(locale, "Trending") %> - <% end %>Invidious</title> <% end %> <%= rendered "components/feed_menu" %> |
