summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--locales/ar.json7
-rw-r--r--locales/de.json7
-rw-r--r--locales/en-US.json7
-rw-r--r--locales/eu.json563
-rw-r--r--locales/fr.json7
-rw-r--r--locales/it.json7
-rw-r--r--locales/nb_NO.json563
-rw-r--r--locales/nl.json7
-rw-r--r--locales/pl.json563
-rw-r--r--locales/ru.json575
-rw-r--r--src/invidious.cr247
-rw-r--r--src/invidious/helpers/helpers.cr19
-rw-r--r--src/invidious/jobs.cr6
-rw-r--r--src/invidious/users.cr6
-rw-r--r--src/invidious/views/components/feed_menu.ecr10
-rw-r--r--src/invidious/views/index.ecr14
-rw-r--r--src/invidious/views/login.ecr56
-rw-r--r--src/invidious/views/popular.ecr2
-rw-r--r--src/invidious/views/preferences.ecr82
-rw-r--r--src/invidious/views/subscriptions.ecr2
-rw-r--r--src/invidious/views/template.ecr52
-rw-r--r--src/invidious/views/top.ecr2
-rw-r--r--src/invidious/views/trending.ecr2
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" %>